diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/packages/loaders/src/spritesheetParser.js b/packages/loaders/src/spritesheetParser.js deleted file mode 100644 index f7fa206..0000000 --- a/packages/loaders/src/spritesheetParser.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Resource } from 'resource-loader'; -import url from 'url'; -import { Spritesheet } from '@pixi/spritesheet'; - -export default function () -{ - return function spritesheetParser(resource, next) - { - const imageResourceName = `${resource.name}_image`; - - // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists - if (!resource.data - || resource.type !== Resource.TYPE.JSON - || !resource.data.frames - || this.resources[imageResourceName] - ) - { - next(); - - return; - } - - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - const resourcePath = getResourcePath(resource, this.baseUrl); - - // load the image for this sheet - this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) - { - const spritesheet = new Spritesheet( - res.texture.baseTexture, - resource.data, - resource.url - ); - - spritesheet.parse(() => - { - resource.spritesheet = spritesheet; - resource.textures = spritesheet.textures; - next(); - }); - }); - }; -} - -export function getResourcePath(resource, baseUrl) -{ - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - return resource.data.meta.image; - } - - return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); -} diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/packages/loaders/src/spritesheetParser.js b/packages/loaders/src/spritesheetParser.js deleted file mode 100644 index f7fa206..0000000 --- a/packages/loaders/src/spritesheetParser.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Resource } from 'resource-loader'; -import url from 'url'; -import { Spritesheet } from '@pixi/spritesheet'; - -export default function () -{ - return function spritesheetParser(resource, next) - { - const imageResourceName = `${resource.name}_image`; - - // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists - if (!resource.data - || resource.type !== Resource.TYPE.JSON - || !resource.data.frames - || this.resources[imageResourceName] - ) - { - next(); - - return; - } - - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - const resourcePath = getResourcePath(resource, this.baseUrl); - - // load the image for this sheet - this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) - { - const spritesheet = new Spritesheet( - res.texture.baseTexture, - resource.data, - resource.url - ); - - spritesheet.parse(() => - { - resource.spritesheet = spritesheet; - resource.textures = spritesheet.textures; - next(); - }); - }); - }; -} - -export function getResourcePath(resource, baseUrl) -{ - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - return resource.data.meta.image; - } - - return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); -} diff --git a/packages/loaders/test/bitmapFontParser.js b/packages/loaders/test/bitmapFontParser.js deleted file mode 100644 index 43ef194..0000000 --- a/packages/loaders/test/bitmapFontParser.js +++ /dev/null @@ -1,325 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const { BitmapText } = require('@pixi/text-bitmap'); -const { BaseTextureCache, TextureCache } = require('@pixi/utils'); -const { Texture, BaseTexture } = require('@pixi/core'); -const { Spritesheet } = require('@pixi/spritesheet'); -const { bitmapFontParser, parseBitmapFontData } = require('../'); - -describe('PIXI.loaders.bitmapFontParser', function () -{ - afterEach(function () - { - for (const font in BitmapText.fonts) - { - delete BitmapText.fonts[font]; - } - for (const baseTexture in BaseTextureCache) - { - delete BaseTextureCache[baseTexture]; - } - for (const texture in TextureCache) - { - delete TextureCache[texture]; - } - }); - - before(function (done) - { - const resolveURL = (url) => path.resolve(this.resources, url); - - this.resources = path.join(__dirname, 'resources'); - this.fontXML = null; - this.fontScaledXML = null; - this.fontImage = null; - this.fontScaledImage = null; - this.atlasImage = null; - this.atlasScaledImage = null; - this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require - this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require - - const loadXML = (url) => new Promise((resolve) => - fs.readFile(resolveURL(url), 'utf8', (err, data) => - { - expect(err).to.be.null; - resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); - })); - - const loadImage = (url) => new Promise((resolve) => - { - const image = new Image(); - - image.onload = () => resolve(image); - image.src = resolveURL(url); - }); - - Promise.all([ - loadXML('font.fnt'), - loadXML('font@0.5x.fnt'), - loadImage('font.png'), - loadImage('font@0.5x.png'), - loadImage('atlas.png'), - loadImage('atlas@0.5x.png'), - ]).then(([ - fontXML, - fontScaledXML, - fontImage, - fontScaledImage, - atlasImage, - atlasScaledImage, - ]) => - { - this.fontXML = fontXML; - this.fontScaledXML = fontScaledXML; - this.fontImage = fontImage; - this.fontScaledImage = fontScaledImage; - this.atlasImage = atlasImage; - this.atlasScaledImage = atlasScaledImage; - done(); - }); - }); - - it('should exist and return a function', function () - { - expect(bitmapFontParser).to.be.a('function'); - expect(bitmapFontParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not XML', function () - { - const spy = sinon.spy(); - const res = {}; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is not properly formatted XML', function () - { - const spy = sinon.spy(); - const res = { data: document.createDocumentFragment() }; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - // TODO: Test the texture cache code path. - // TODO: Test the loading texture code path. - // TODO: Test data-url code paths. - - it('should properly register bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charA.texture.frame.x).to.equal(2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charB.texture.frame.x).to.equal(2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charC.texture.frame.x).to.equal(23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charD.texture.frame.x).to.equal(19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register SCALED bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); - const font = BitmapText.registerFont(this.fontScaledXML, 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register bitmap font NESTED into spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); - - it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); - - spritesheet.resolution = 1; - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); -}); - -describe('PIXI.loaders.parseBitmapFontData', function () -{ - it('should exist', function () - { - expect(parseBitmapFontData).to.be.a('function'); - }); - - // TODO: Test the parser code. -}); diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/packages/loaders/src/spritesheetParser.js b/packages/loaders/src/spritesheetParser.js deleted file mode 100644 index f7fa206..0000000 --- a/packages/loaders/src/spritesheetParser.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Resource } from 'resource-loader'; -import url from 'url'; -import { Spritesheet } from '@pixi/spritesheet'; - -export default function () -{ - return function spritesheetParser(resource, next) - { - const imageResourceName = `${resource.name}_image`; - - // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists - if (!resource.data - || resource.type !== Resource.TYPE.JSON - || !resource.data.frames - || this.resources[imageResourceName] - ) - { - next(); - - return; - } - - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - const resourcePath = getResourcePath(resource, this.baseUrl); - - // load the image for this sheet - this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) - { - const spritesheet = new Spritesheet( - res.texture.baseTexture, - resource.data, - resource.url - ); - - spritesheet.parse(() => - { - resource.spritesheet = spritesheet; - resource.textures = spritesheet.textures; - next(); - }); - }); - }; -} - -export function getResourcePath(resource, baseUrl) -{ - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - return resource.data.meta.image; - } - - return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); -} diff --git a/packages/loaders/test/bitmapFontParser.js b/packages/loaders/test/bitmapFontParser.js deleted file mode 100644 index 43ef194..0000000 --- a/packages/loaders/test/bitmapFontParser.js +++ /dev/null @@ -1,325 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const { BitmapText } = require('@pixi/text-bitmap'); -const { BaseTextureCache, TextureCache } = require('@pixi/utils'); -const { Texture, BaseTexture } = require('@pixi/core'); -const { Spritesheet } = require('@pixi/spritesheet'); -const { bitmapFontParser, parseBitmapFontData } = require('../'); - -describe('PIXI.loaders.bitmapFontParser', function () -{ - afterEach(function () - { - for (const font in BitmapText.fonts) - { - delete BitmapText.fonts[font]; - } - for (const baseTexture in BaseTextureCache) - { - delete BaseTextureCache[baseTexture]; - } - for (const texture in TextureCache) - { - delete TextureCache[texture]; - } - }); - - before(function (done) - { - const resolveURL = (url) => path.resolve(this.resources, url); - - this.resources = path.join(__dirname, 'resources'); - this.fontXML = null; - this.fontScaledXML = null; - this.fontImage = null; - this.fontScaledImage = null; - this.atlasImage = null; - this.atlasScaledImage = null; - this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require - this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require - - const loadXML = (url) => new Promise((resolve) => - fs.readFile(resolveURL(url), 'utf8', (err, data) => - { - expect(err).to.be.null; - resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); - })); - - const loadImage = (url) => new Promise((resolve) => - { - const image = new Image(); - - image.onload = () => resolve(image); - image.src = resolveURL(url); - }); - - Promise.all([ - loadXML('font.fnt'), - loadXML('font@0.5x.fnt'), - loadImage('font.png'), - loadImage('font@0.5x.png'), - loadImage('atlas.png'), - loadImage('atlas@0.5x.png'), - ]).then(([ - fontXML, - fontScaledXML, - fontImage, - fontScaledImage, - atlasImage, - atlasScaledImage, - ]) => - { - this.fontXML = fontXML; - this.fontScaledXML = fontScaledXML; - this.fontImage = fontImage; - this.fontScaledImage = fontScaledImage; - this.atlasImage = atlasImage; - this.atlasScaledImage = atlasScaledImage; - done(); - }); - }); - - it('should exist and return a function', function () - { - expect(bitmapFontParser).to.be.a('function'); - expect(bitmapFontParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not XML', function () - { - const spy = sinon.spy(); - const res = {}; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is not properly formatted XML', function () - { - const spy = sinon.spy(); - const res = { data: document.createDocumentFragment() }; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - // TODO: Test the texture cache code path. - // TODO: Test the loading texture code path. - // TODO: Test data-url code paths. - - it('should properly register bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charA.texture.frame.x).to.equal(2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charB.texture.frame.x).to.equal(2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charC.texture.frame.x).to.equal(23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charD.texture.frame.x).to.equal(19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register SCALED bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); - const font = BitmapText.registerFont(this.fontScaledXML, 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register bitmap font NESTED into spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); - - it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); - - spritesheet.resolution = 1; - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); -}); - -describe('PIXI.loaders.parseBitmapFontData', function () -{ - it('should exist', function () - { - expect(parseBitmapFontData).to.be.a('function'); - }); - - // TODO: Test the parser code. -}); diff --git a/packages/loaders/test/index.js b/packages/loaders/test/index.js index f00de5f..72c6554 100644 --- a/packages/loaders/test/index.js +++ b/packages/loaders/test/index.js @@ -1,4 +1,2 @@ -require('./bitmapFontParser'); require('./loader'); -require('./spritesheetParser'); require('./textureParser'); diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/packages/loaders/src/spritesheetParser.js b/packages/loaders/src/spritesheetParser.js deleted file mode 100644 index f7fa206..0000000 --- a/packages/loaders/src/spritesheetParser.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Resource } from 'resource-loader'; -import url from 'url'; -import { Spritesheet } from '@pixi/spritesheet'; - -export default function () -{ - return function spritesheetParser(resource, next) - { - const imageResourceName = `${resource.name}_image`; - - // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists - if (!resource.data - || resource.type !== Resource.TYPE.JSON - || !resource.data.frames - || this.resources[imageResourceName] - ) - { - next(); - - return; - } - - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - const resourcePath = getResourcePath(resource, this.baseUrl); - - // load the image for this sheet - this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) - { - const spritesheet = new Spritesheet( - res.texture.baseTexture, - resource.data, - resource.url - ); - - spritesheet.parse(() => - { - resource.spritesheet = spritesheet; - resource.textures = spritesheet.textures; - next(); - }); - }); - }; -} - -export function getResourcePath(resource, baseUrl) -{ - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - return resource.data.meta.image; - } - - return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); -} diff --git a/packages/loaders/test/bitmapFontParser.js b/packages/loaders/test/bitmapFontParser.js deleted file mode 100644 index 43ef194..0000000 --- a/packages/loaders/test/bitmapFontParser.js +++ /dev/null @@ -1,325 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const { BitmapText } = require('@pixi/text-bitmap'); -const { BaseTextureCache, TextureCache } = require('@pixi/utils'); -const { Texture, BaseTexture } = require('@pixi/core'); -const { Spritesheet } = require('@pixi/spritesheet'); -const { bitmapFontParser, parseBitmapFontData } = require('../'); - -describe('PIXI.loaders.bitmapFontParser', function () -{ - afterEach(function () - { - for (const font in BitmapText.fonts) - { - delete BitmapText.fonts[font]; - } - for (const baseTexture in BaseTextureCache) - { - delete BaseTextureCache[baseTexture]; - } - for (const texture in TextureCache) - { - delete TextureCache[texture]; - } - }); - - before(function (done) - { - const resolveURL = (url) => path.resolve(this.resources, url); - - this.resources = path.join(__dirname, 'resources'); - this.fontXML = null; - this.fontScaledXML = null; - this.fontImage = null; - this.fontScaledImage = null; - this.atlasImage = null; - this.atlasScaledImage = null; - this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require - this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require - - const loadXML = (url) => new Promise((resolve) => - fs.readFile(resolveURL(url), 'utf8', (err, data) => - { - expect(err).to.be.null; - resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); - })); - - const loadImage = (url) => new Promise((resolve) => - { - const image = new Image(); - - image.onload = () => resolve(image); - image.src = resolveURL(url); - }); - - Promise.all([ - loadXML('font.fnt'), - loadXML('font@0.5x.fnt'), - loadImage('font.png'), - loadImage('font@0.5x.png'), - loadImage('atlas.png'), - loadImage('atlas@0.5x.png'), - ]).then(([ - fontXML, - fontScaledXML, - fontImage, - fontScaledImage, - atlasImage, - atlasScaledImage, - ]) => - { - this.fontXML = fontXML; - this.fontScaledXML = fontScaledXML; - this.fontImage = fontImage; - this.fontScaledImage = fontScaledImage; - this.atlasImage = atlasImage; - this.atlasScaledImage = atlasScaledImage; - done(); - }); - }); - - it('should exist and return a function', function () - { - expect(bitmapFontParser).to.be.a('function'); - expect(bitmapFontParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not XML', function () - { - const spy = sinon.spy(); - const res = {}; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is not properly formatted XML', function () - { - const spy = sinon.spy(); - const res = { data: document.createDocumentFragment() }; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - // TODO: Test the texture cache code path. - // TODO: Test the loading texture code path. - // TODO: Test data-url code paths. - - it('should properly register bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charA.texture.frame.x).to.equal(2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charB.texture.frame.x).to.equal(2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charC.texture.frame.x).to.equal(23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charD.texture.frame.x).to.equal(19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register SCALED bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); - const font = BitmapText.registerFont(this.fontScaledXML, 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register bitmap font NESTED into spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); - - it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); - - spritesheet.resolution = 1; - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); -}); - -describe('PIXI.loaders.parseBitmapFontData', function () -{ - it('should exist', function () - { - expect(parseBitmapFontData).to.be.a('function'); - }); - - // TODO: Test the parser code. -}); diff --git a/packages/loaders/test/index.js b/packages/loaders/test/index.js index f00de5f..72c6554 100644 --- a/packages/loaders/test/index.js +++ b/packages/loaders/test/index.js @@ -1,4 +1,2 @@ -require('./bitmapFontParser'); require('./loader'); -require('./spritesheetParser'); require('./textureParser'); diff --git a/packages/loaders/test/resources/atlas.json b/packages/loaders/test/resources/atlas.json deleted file mode 100644 index 86e65a5..0000000 --- a/packages/loaders/test/resources/atlas.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/packages/loaders/src/spritesheetParser.js b/packages/loaders/src/spritesheetParser.js deleted file mode 100644 index f7fa206..0000000 --- a/packages/loaders/src/spritesheetParser.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Resource } from 'resource-loader'; -import url from 'url'; -import { Spritesheet } from '@pixi/spritesheet'; - -export default function () -{ - return function spritesheetParser(resource, next) - { - const imageResourceName = `${resource.name}_image`; - - // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists - if (!resource.data - || resource.type !== Resource.TYPE.JSON - || !resource.data.frames - || this.resources[imageResourceName] - ) - { - next(); - - return; - } - - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - const resourcePath = getResourcePath(resource, this.baseUrl); - - // load the image for this sheet - this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) - { - const spritesheet = new Spritesheet( - res.texture.baseTexture, - resource.data, - resource.url - ); - - spritesheet.parse(() => - { - resource.spritesheet = spritesheet; - resource.textures = spritesheet.textures; - next(); - }); - }); - }; -} - -export function getResourcePath(resource, baseUrl) -{ - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - return resource.data.meta.image; - } - - return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); -} diff --git a/packages/loaders/test/bitmapFontParser.js b/packages/loaders/test/bitmapFontParser.js deleted file mode 100644 index 43ef194..0000000 --- a/packages/loaders/test/bitmapFontParser.js +++ /dev/null @@ -1,325 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const { BitmapText } = require('@pixi/text-bitmap'); -const { BaseTextureCache, TextureCache } = require('@pixi/utils'); -const { Texture, BaseTexture } = require('@pixi/core'); -const { Spritesheet } = require('@pixi/spritesheet'); -const { bitmapFontParser, parseBitmapFontData } = require('../'); - -describe('PIXI.loaders.bitmapFontParser', function () -{ - afterEach(function () - { - for (const font in BitmapText.fonts) - { - delete BitmapText.fonts[font]; - } - for (const baseTexture in BaseTextureCache) - { - delete BaseTextureCache[baseTexture]; - } - for (const texture in TextureCache) - { - delete TextureCache[texture]; - } - }); - - before(function (done) - { - const resolveURL = (url) => path.resolve(this.resources, url); - - this.resources = path.join(__dirname, 'resources'); - this.fontXML = null; - this.fontScaledXML = null; - this.fontImage = null; - this.fontScaledImage = null; - this.atlasImage = null; - this.atlasScaledImage = null; - this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require - this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require - - const loadXML = (url) => new Promise((resolve) => - fs.readFile(resolveURL(url), 'utf8', (err, data) => - { - expect(err).to.be.null; - resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); - })); - - const loadImage = (url) => new Promise((resolve) => - { - const image = new Image(); - - image.onload = () => resolve(image); - image.src = resolveURL(url); - }); - - Promise.all([ - loadXML('font.fnt'), - loadXML('font@0.5x.fnt'), - loadImage('font.png'), - loadImage('font@0.5x.png'), - loadImage('atlas.png'), - loadImage('atlas@0.5x.png'), - ]).then(([ - fontXML, - fontScaledXML, - fontImage, - fontScaledImage, - atlasImage, - atlasScaledImage, - ]) => - { - this.fontXML = fontXML; - this.fontScaledXML = fontScaledXML; - this.fontImage = fontImage; - this.fontScaledImage = fontScaledImage; - this.atlasImage = atlasImage; - this.atlasScaledImage = atlasScaledImage; - done(); - }); - }); - - it('should exist and return a function', function () - { - expect(bitmapFontParser).to.be.a('function'); - expect(bitmapFontParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not XML', function () - { - const spy = sinon.spy(); - const res = {}; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is not properly formatted XML', function () - { - const spy = sinon.spy(); - const res = { data: document.createDocumentFragment() }; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - // TODO: Test the texture cache code path. - // TODO: Test the loading texture code path. - // TODO: Test data-url code paths. - - it('should properly register bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charA.texture.frame.x).to.equal(2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charB.texture.frame.x).to.equal(2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charC.texture.frame.x).to.equal(23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charD.texture.frame.x).to.equal(19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register SCALED bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); - const font = BitmapText.registerFont(this.fontScaledXML, 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register bitmap font NESTED into spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); - - it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); - - spritesheet.resolution = 1; - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); -}); - -describe('PIXI.loaders.parseBitmapFontData', function () -{ - it('should exist', function () - { - expect(parseBitmapFontData).to.be.a('function'); - }); - - // TODO: Test the parser code. -}); diff --git a/packages/loaders/test/index.js b/packages/loaders/test/index.js index f00de5f..72c6554 100644 --- a/packages/loaders/test/index.js +++ b/packages/loaders/test/index.js @@ -1,4 +1,2 @@ -require('./bitmapFontParser'); require('./loader'); -require('./spritesheetParser'); require('./textureParser'); diff --git a/packages/loaders/test/resources/atlas.json b/packages/loaders/test/resources/atlas.json deleted file mode 100644 index 86e65a5..0000000 --- a/packages/loaders/test/resources/atlas.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas.png b/packages/loaders/test/resources/atlas.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas.png +++ /dev/null Binary files differ diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/packages/loaders/src/spritesheetParser.js b/packages/loaders/src/spritesheetParser.js deleted file mode 100644 index f7fa206..0000000 --- a/packages/loaders/src/spritesheetParser.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Resource } from 'resource-loader'; -import url from 'url'; -import { Spritesheet } from '@pixi/spritesheet'; - -export default function () -{ - return function spritesheetParser(resource, next) - { - const imageResourceName = `${resource.name}_image`; - - // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists - if (!resource.data - || resource.type !== Resource.TYPE.JSON - || !resource.data.frames - || this.resources[imageResourceName] - ) - { - next(); - - return; - } - - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - const resourcePath = getResourcePath(resource, this.baseUrl); - - // load the image for this sheet - this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) - { - const spritesheet = new Spritesheet( - res.texture.baseTexture, - resource.data, - resource.url - ); - - spritesheet.parse(() => - { - resource.spritesheet = spritesheet; - resource.textures = spritesheet.textures; - next(); - }); - }); - }; -} - -export function getResourcePath(resource, baseUrl) -{ - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - return resource.data.meta.image; - } - - return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); -} diff --git a/packages/loaders/test/bitmapFontParser.js b/packages/loaders/test/bitmapFontParser.js deleted file mode 100644 index 43ef194..0000000 --- a/packages/loaders/test/bitmapFontParser.js +++ /dev/null @@ -1,325 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const { BitmapText } = require('@pixi/text-bitmap'); -const { BaseTextureCache, TextureCache } = require('@pixi/utils'); -const { Texture, BaseTexture } = require('@pixi/core'); -const { Spritesheet } = require('@pixi/spritesheet'); -const { bitmapFontParser, parseBitmapFontData } = require('../'); - -describe('PIXI.loaders.bitmapFontParser', function () -{ - afterEach(function () - { - for (const font in BitmapText.fonts) - { - delete BitmapText.fonts[font]; - } - for (const baseTexture in BaseTextureCache) - { - delete BaseTextureCache[baseTexture]; - } - for (const texture in TextureCache) - { - delete TextureCache[texture]; - } - }); - - before(function (done) - { - const resolveURL = (url) => path.resolve(this.resources, url); - - this.resources = path.join(__dirname, 'resources'); - this.fontXML = null; - this.fontScaledXML = null; - this.fontImage = null; - this.fontScaledImage = null; - this.atlasImage = null; - this.atlasScaledImage = null; - this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require - this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require - - const loadXML = (url) => new Promise((resolve) => - fs.readFile(resolveURL(url), 'utf8', (err, data) => - { - expect(err).to.be.null; - resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); - })); - - const loadImage = (url) => new Promise((resolve) => - { - const image = new Image(); - - image.onload = () => resolve(image); - image.src = resolveURL(url); - }); - - Promise.all([ - loadXML('font.fnt'), - loadXML('font@0.5x.fnt'), - loadImage('font.png'), - loadImage('font@0.5x.png'), - loadImage('atlas.png'), - loadImage('atlas@0.5x.png'), - ]).then(([ - fontXML, - fontScaledXML, - fontImage, - fontScaledImage, - atlasImage, - atlasScaledImage, - ]) => - { - this.fontXML = fontXML; - this.fontScaledXML = fontScaledXML; - this.fontImage = fontImage; - this.fontScaledImage = fontScaledImage; - this.atlasImage = atlasImage; - this.atlasScaledImage = atlasScaledImage; - done(); - }); - }); - - it('should exist and return a function', function () - { - expect(bitmapFontParser).to.be.a('function'); - expect(bitmapFontParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not XML', function () - { - const spy = sinon.spy(); - const res = {}; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is not properly formatted XML', function () - { - const spy = sinon.spy(); - const res = { data: document.createDocumentFragment() }; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - // TODO: Test the texture cache code path. - // TODO: Test the loading texture code path. - // TODO: Test data-url code paths. - - it('should properly register bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charA.texture.frame.x).to.equal(2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charB.texture.frame.x).to.equal(2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charC.texture.frame.x).to.equal(23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charD.texture.frame.x).to.equal(19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register SCALED bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); - const font = BitmapText.registerFont(this.fontScaledXML, 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register bitmap font NESTED into spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); - - it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); - - spritesheet.resolution = 1; - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); -}); - -describe('PIXI.loaders.parseBitmapFontData', function () -{ - it('should exist', function () - { - expect(parseBitmapFontData).to.be.a('function'); - }); - - // TODO: Test the parser code. -}); diff --git a/packages/loaders/test/index.js b/packages/loaders/test/index.js index f00de5f..72c6554 100644 --- a/packages/loaders/test/index.js +++ b/packages/loaders/test/index.js @@ -1,4 +1,2 @@ -require('./bitmapFontParser'); require('./loader'); -require('./spritesheetParser'); require('./textureParser'); diff --git a/packages/loaders/test/resources/atlas.json b/packages/loaders/test/resources/atlas.json deleted file mode 100644 index 86e65a5..0000000 --- a/packages/loaders/test/resources/atlas.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas.png b/packages/loaders/test/resources/atlas.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/atlas@0.5x.json b/packages/loaders/test/resources/atlas@0.5x.json deleted file mode 100644 index ae990a1..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas@0.5x.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/packages/loaders/src/spritesheetParser.js b/packages/loaders/src/spritesheetParser.js deleted file mode 100644 index f7fa206..0000000 --- a/packages/loaders/src/spritesheetParser.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Resource } from 'resource-loader'; -import url from 'url'; -import { Spritesheet } from '@pixi/spritesheet'; - -export default function () -{ - return function spritesheetParser(resource, next) - { - const imageResourceName = `${resource.name}_image`; - - // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists - if (!resource.data - || resource.type !== Resource.TYPE.JSON - || !resource.data.frames - || this.resources[imageResourceName] - ) - { - next(); - - return; - } - - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - const resourcePath = getResourcePath(resource, this.baseUrl); - - // load the image for this sheet - this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) - { - const spritesheet = new Spritesheet( - res.texture.baseTexture, - resource.data, - resource.url - ); - - spritesheet.parse(() => - { - resource.spritesheet = spritesheet; - resource.textures = spritesheet.textures; - next(); - }); - }); - }; -} - -export function getResourcePath(resource, baseUrl) -{ - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - return resource.data.meta.image; - } - - return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); -} diff --git a/packages/loaders/test/bitmapFontParser.js b/packages/loaders/test/bitmapFontParser.js deleted file mode 100644 index 43ef194..0000000 --- a/packages/loaders/test/bitmapFontParser.js +++ /dev/null @@ -1,325 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const { BitmapText } = require('@pixi/text-bitmap'); -const { BaseTextureCache, TextureCache } = require('@pixi/utils'); -const { Texture, BaseTexture } = require('@pixi/core'); -const { Spritesheet } = require('@pixi/spritesheet'); -const { bitmapFontParser, parseBitmapFontData } = require('../'); - -describe('PIXI.loaders.bitmapFontParser', function () -{ - afterEach(function () - { - for (const font in BitmapText.fonts) - { - delete BitmapText.fonts[font]; - } - for (const baseTexture in BaseTextureCache) - { - delete BaseTextureCache[baseTexture]; - } - for (const texture in TextureCache) - { - delete TextureCache[texture]; - } - }); - - before(function (done) - { - const resolveURL = (url) => path.resolve(this.resources, url); - - this.resources = path.join(__dirname, 'resources'); - this.fontXML = null; - this.fontScaledXML = null; - this.fontImage = null; - this.fontScaledImage = null; - this.atlasImage = null; - this.atlasScaledImage = null; - this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require - this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require - - const loadXML = (url) => new Promise((resolve) => - fs.readFile(resolveURL(url), 'utf8', (err, data) => - { - expect(err).to.be.null; - resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); - })); - - const loadImage = (url) => new Promise((resolve) => - { - const image = new Image(); - - image.onload = () => resolve(image); - image.src = resolveURL(url); - }); - - Promise.all([ - loadXML('font.fnt'), - loadXML('font@0.5x.fnt'), - loadImage('font.png'), - loadImage('font@0.5x.png'), - loadImage('atlas.png'), - loadImage('atlas@0.5x.png'), - ]).then(([ - fontXML, - fontScaledXML, - fontImage, - fontScaledImage, - atlasImage, - atlasScaledImage, - ]) => - { - this.fontXML = fontXML; - this.fontScaledXML = fontScaledXML; - this.fontImage = fontImage; - this.fontScaledImage = fontScaledImage; - this.atlasImage = atlasImage; - this.atlasScaledImage = atlasScaledImage; - done(); - }); - }); - - it('should exist and return a function', function () - { - expect(bitmapFontParser).to.be.a('function'); - expect(bitmapFontParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not XML', function () - { - const spy = sinon.spy(); - const res = {}; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is not properly formatted XML', function () - { - const spy = sinon.spy(); - const res = { data: document.createDocumentFragment() }; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - // TODO: Test the texture cache code path. - // TODO: Test the loading texture code path. - // TODO: Test data-url code paths. - - it('should properly register bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charA.texture.frame.x).to.equal(2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charB.texture.frame.x).to.equal(2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charC.texture.frame.x).to.equal(23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charD.texture.frame.x).to.equal(19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register SCALED bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); - const font = BitmapText.registerFont(this.fontScaledXML, 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register bitmap font NESTED into spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); - - it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); - - spritesheet.resolution = 1; - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); -}); - -describe('PIXI.loaders.parseBitmapFontData', function () -{ - it('should exist', function () - { - expect(parseBitmapFontData).to.be.a('function'); - }); - - // TODO: Test the parser code. -}); diff --git a/packages/loaders/test/index.js b/packages/loaders/test/index.js index f00de5f..72c6554 100644 --- a/packages/loaders/test/index.js +++ b/packages/loaders/test/index.js @@ -1,4 +1,2 @@ -require('./bitmapFontParser'); require('./loader'); -require('./spritesheetParser'); require('./textureParser'); diff --git a/packages/loaders/test/resources/atlas.json b/packages/loaders/test/resources/atlas.json deleted file mode 100644 index 86e65a5..0000000 --- a/packages/loaders/test/resources/atlas.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas.png b/packages/loaders/test/resources/atlas.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/atlas@0.5x.json b/packages/loaders/test/resources/atlas@0.5x.json deleted file mode 100644 index ae990a1..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas@0.5x.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas@0.5x.png b/packages/loaders/test/resources/atlas@0.5x.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.png +++ /dev/null Binary files differ diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/packages/loaders/src/spritesheetParser.js b/packages/loaders/src/spritesheetParser.js deleted file mode 100644 index f7fa206..0000000 --- a/packages/loaders/src/spritesheetParser.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Resource } from 'resource-loader'; -import url from 'url'; -import { Spritesheet } from '@pixi/spritesheet'; - -export default function () -{ - return function spritesheetParser(resource, next) - { - const imageResourceName = `${resource.name}_image`; - - // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists - if (!resource.data - || resource.type !== Resource.TYPE.JSON - || !resource.data.frames - || this.resources[imageResourceName] - ) - { - next(); - - return; - } - - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - const resourcePath = getResourcePath(resource, this.baseUrl); - - // load the image for this sheet - this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) - { - const spritesheet = new Spritesheet( - res.texture.baseTexture, - resource.data, - resource.url - ); - - spritesheet.parse(() => - { - resource.spritesheet = spritesheet; - resource.textures = spritesheet.textures; - next(); - }); - }); - }; -} - -export function getResourcePath(resource, baseUrl) -{ - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - return resource.data.meta.image; - } - - return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); -} diff --git a/packages/loaders/test/bitmapFontParser.js b/packages/loaders/test/bitmapFontParser.js deleted file mode 100644 index 43ef194..0000000 --- a/packages/loaders/test/bitmapFontParser.js +++ /dev/null @@ -1,325 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const { BitmapText } = require('@pixi/text-bitmap'); -const { BaseTextureCache, TextureCache } = require('@pixi/utils'); -const { Texture, BaseTexture } = require('@pixi/core'); -const { Spritesheet } = require('@pixi/spritesheet'); -const { bitmapFontParser, parseBitmapFontData } = require('../'); - -describe('PIXI.loaders.bitmapFontParser', function () -{ - afterEach(function () - { - for (const font in BitmapText.fonts) - { - delete BitmapText.fonts[font]; - } - for (const baseTexture in BaseTextureCache) - { - delete BaseTextureCache[baseTexture]; - } - for (const texture in TextureCache) - { - delete TextureCache[texture]; - } - }); - - before(function (done) - { - const resolveURL = (url) => path.resolve(this.resources, url); - - this.resources = path.join(__dirname, 'resources'); - this.fontXML = null; - this.fontScaledXML = null; - this.fontImage = null; - this.fontScaledImage = null; - this.atlasImage = null; - this.atlasScaledImage = null; - this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require - this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require - - const loadXML = (url) => new Promise((resolve) => - fs.readFile(resolveURL(url), 'utf8', (err, data) => - { - expect(err).to.be.null; - resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); - })); - - const loadImage = (url) => new Promise((resolve) => - { - const image = new Image(); - - image.onload = () => resolve(image); - image.src = resolveURL(url); - }); - - Promise.all([ - loadXML('font.fnt'), - loadXML('font@0.5x.fnt'), - loadImage('font.png'), - loadImage('font@0.5x.png'), - loadImage('atlas.png'), - loadImage('atlas@0.5x.png'), - ]).then(([ - fontXML, - fontScaledXML, - fontImage, - fontScaledImage, - atlasImage, - atlasScaledImage, - ]) => - { - this.fontXML = fontXML; - this.fontScaledXML = fontScaledXML; - this.fontImage = fontImage; - this.fontScaledImage = fontScaledImage; - this.atlasImage = atlasImage; - this.atlasScaledImage = atlasScaledImage; - done(); - }); - }); - - it('should exist and return a function', function () - { - expect(bitmapFontParser).to.be.a('function'); - expect(bitmapFontParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not XML', function () - { - const spy = sinon.spy(); - const res = {}; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is not properly formatted XML', function () - { - const spy = sinon.spy(); - const res = { data: document.createDocumentFragment() }; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - // TODO: Test the texture cache code path. - // TODO: Test the loading texture code path. - // TODO: Test data-url code paths. - - it('should properly register bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charA.texture.frame.x).to.equal(2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charB.texture.frame.x).to.equal(2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charC.texture.frame.x).to.equal(23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charD.texture.frame.x).to.equal(19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register SCALED bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); - const font = BitmapText.registerFont(this.fontScaledXML, 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register bitmap font NESTED into spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); - - it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); - - spritesheet.resolution = 1; - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); -}); - -describe('PIXI.loaders.parseBitmapFontData', function () -{ - it('should exist', function () - { - expect(parseBitmapFontData).to.be.a('function'); - }); - - // TODO: Test the parser code. -}); diff --git a/packages/loaders/test/index.js b/packages/loaders/test/index.js index f00de5f..72c6554 100644 --- a/packages/loaders/test/index.js +++ b/packages/loaders/test/index.js @@ -1,4 +1,2 @@ -require('./bitmapFontParser'); require('./loader'); -require('./spritesheetParser'); require('./textureParser'); diff --git a/packages/loaders/test/resources/atlas.json b/packages/loaders/test/resources/atlas.json deleted file mode 100644 index 86e65a5..0000000 --- a/packages/loaders/test/resources/atlas.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas.png b/packages/loaders/test/resources/atlas.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/atlas@0.5x.json b/packages/loaders/test/resources/atlas@0.5x.json deleted file mode 100644 index ae990a1..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas@0.5x.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas@0.5x.png b/packages/loaders/test/resources/atlas@0.5x.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font.fnt b/packages/loaders/test/resources/font.fnt deleted file mode 100644 index 56e1060..0000000 --- a/packages/loaders/test/resources/font.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/packages/loaders/src/spritesheetParser.js b/packages/loaders/src/spritesheetParser.js deleted file mode 100644 index f7fa206..0000000 --- a/packages/loaders/src/spritesheetParser.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Resource } from 'resource-loader'; -import url from 'url'; -import { Spritesheet } from '@pixi/spritesheet'; - -export default function () -{ - return function spritesheetParser(resource, next) - { - const imageResourceName = `${resource.name}_image`; - - // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists - if (!resource.data - || resource.type !== Resource.TYPE.JSON - || !resource.data.frames - || this.resources[imageResourceName] - ) - { - next(); - - return; - } - - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - const resourcePath = getResourcePath(resource, this.baseUrl); - - // load the image for this sheet - this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) - { - const spritesheet = new Spritesheet( - res.texture.baseTexture, - resource.data, - resource.url - ); - - spritesheet.parse(() => - { - resource.spritesheet = spritesheet; - resource.textures = spritesheet.textures; - next(); - }); - }); - }; -} - -export function getResourcePath(resource, baseUrl) -{ - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - return resource.data.meta.image; - } - - return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); -} diff --git a/packages/loaders/test/bitmapFontParser.js b/packages/loaders/test/bitmapFontParser.js deleted file mode 100644 index 43ef194..0000000 --- a/packages/loaders/test/bitmapFontParser.js +++ /dev/null @@ -1,325 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const { BitmapText } = require('@pixi/text-bitmap'); -const { BaseTextureCache, TextureCache } = require('@pixi/utils'); -const { Texture, BaseTexture } = require('@pixi/core'); -const { Spritesheet } = require('@pixi/spritesheet'); -const { bitmapFontParser, parseBitmapFontData } = require('../'); - -describe('PIXI.loaders.bitmapFontParser', function () -{ - afterEach(function () - { - for (const font in BitmapText.fonts) - { - delete BitmapText.fonts[font]; - } - for (const baseTexture in BaseTextureCache) - { - delete BaseTextureCache[baseTexture]; - } - for (const texture in TextureCache) - { - delete TextureCache[texture]; - } - }); - - before(function (done) - { - const resolveURL = (url) => path.resolve(this.resources, url); - - this.resources = path.join(__dirname, 'resources'); - this.fontXML = null; - this.fontScaledXML = null; - this.fontImage = null; - this.fontScaledImage = null; - this.atlasImage = null; - this.atlasScaledImage = null; - this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require - this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require - - const loadXML = (url) => new Promise((resolve) => - fs.readFile(resolveURL(url), 'utf8', (err, data) => - { - expect(err).to.be.null; - resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); - })); - - const loadImage = (url) => new Promise((resolve) => - { - const image = new Image(); - - image.onload = () => resolve(image); - image.src = resolveURL(url); - }); - - Promise.all([ - loadXML('font.fnt'), - loadXML('font@0.5x.fnt'), - loadImage('font.png'), - loadImage('font@0.5x.png'), - loadImage('atlas.png'), - loadImage('atlas@0.5x.png'), - ]).then(([ - fontXML, - fontScaledXML, - fontImage, - fontScaledImage, - atlasImage, - atlasScaledImage, - ]) => - { - this.fontXML = fontXML; - this.fontScaledXML = fontScaledXML; - this.fontImage = fontImage; - this.fontScaledImage = fontScaledImage; - this.atlasImage = atlasImage; - this.atlasScaledImage = atlasScaledImage; - done(); - }); - }); - - it('should exist and return a function', function () - { - expect(bitmapFontParser).to.be.a('function'); - expect(bitmapFontParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not XML', function () - { - const spy = sinon.spy(); - const res = {}; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is not properly formatted XML', function () - { - const spy = sinon.spy(); - const res = { data: document.createDocumentFragment() }; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - // TODO: Test the texture cache code path. - // TODO: Test the loading texture code path. - // TODO: Test data-url code paths. - - it('should properly register bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charA.texture.frame.x).to.equal(2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charB.texture.frame.x).to.equal(2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charC.texture.frame.x).to.equal(23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charD.texture.frame.x).to.equal(19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register SCALED bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); - const font = BitmapText.registerFont(this.fontScaledXML, 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register bitmap font NESTED into spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); - - it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); - - spritesheet.resolution = 1; - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); -}); - -describe('PIXI.loaders.parseBitmapFontData', function () -{ - it('should exist', function () - { - expect(parseBitmapFontData).to.be.a('function'); - }); - - // TODO: Test the parser code. -}); diff --git a/packages/loaders/test/index.js b/packages/loaders/test/index.js index f00de5f..72c6554 100644 --- a/packages/loaders/test/index.js +++ b/packages/loaders/test/index.js @@ -1,4 +1,2 @@ -require('./bitmapFontParser'); require('./loader'); -require('./spritesheetParser'); require('./textureParser'); diff --git a/packages/loaders/test/resources/atlas.json b/packages/loaders/test/resources/atlas.json deleted file mode 100644 index 86e65a5..0000000 --- a/packages/loaders/test/resources/atlas.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas.png b/packages/loaders/test/resources/atlas.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/atlas@0.5x.json b/packages/loaders/test/resources/atlas@0.5x.json deleted file mode 100644 index ae990a1..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas@0.5x.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas@0.5x.png b/packages/loaders/test/resources/atlas@0.5x.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font.fnt b/packages/loaders/test/resources/font.fnt deleted file mode 100644 index 56e1060..0000000 --- a/packages/loaders/test/resources/font.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font.png b/packages/loaders/test/resources/font.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font.png +++ /dev/null Binary files differ diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/packages/loaders/src/spritesheetParser.js b/packages/loaders/src/spritesheetParser.js deleted file mode 100644 index f7fa206..0000000 --- a/packages/loaders/src/spritesheetParser.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Resource } from 'resource-loader'; -import url from 'url'; -import { Spritesheet } from '@pixi/spritesheet'; - -export default function () -{ - return function spritesheetParser(resource, next) - { - const imageResourceName = `${resource.name}_image`; - - // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists - if (!resource.data - || resource.type !== Resource.TYPE.JSON - || !resource.data.frames - || this.resources[imageResourceName] - ) - { - next(); - - return; - } - - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - const resourcePath = getResourcePath(resource, this.baseUrl); - - // load the image for this sheet - this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) - { - const spritesheet = new Spritesheet( - res.texture.baseTexture, - resource.data, - resource.url - ); - - spritesheet.parse(() => - { - resource.spritesheet = spritesheet; - resource.textures = spritesheet.textures; - next(); - }); - }); - }; -} - -export function getResourcePath(resource, baseUrl) -{ - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - return resource.data.meta.image; - } - - return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); -} diff --git a/packages/loaders/test/bitmapFontParser.js b/packages/loaders/test/bitmapFontParser.js deleted file mode 100644 index 43ef194..0000000 --- a/packages/loaders/test/bitmapFontParser.js +++ /dev/null @@ -1,325 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const { BitmapText } = require('@pixi/text-bitmap'); -const { BaseTextureCache, TextureCache } = require('@pixi/utils'); -const { Texture, BaseTexture } = require('@pixi/core'); -const { Spritesheet } = require('@pixi/spritesheet'); -const { bitmapFontParser, parseBitmapFontData } = require('../'); - -describe('PIXI.loaders.bitmapFontParser', function () -{ - afterEach(function () - { - for (const font in BitmapText.fonts) - { - delete BitmapText.fonts[font]; - } - for (const baseTexture in BaseTextureCache) - { - delete BaseTextureCache[baseTexture]; - } - for (const texture in TextureCache) - { - delete TextureCache[texture]; - } - }); - - before(function (done) - { - const resolveURL = (url) => path.resolve(this.resources, url); - - this.resources = path.join(__dirname, 'resources'); - this.fontXML = null; - this.fontScaledXML = null; - this.fontImage = null; - this.fontScaledImage = null; - this.atlasImage = null; - this.atlasScaledImage = null; - this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require - this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require - - const loadXML = (url) => new Promise((resolve) => - fs.readFile(resolveURL(url), 'utf8', (err, data) => - { - expect(err).to.be.null; - resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); - })); - - const loadImage = (url) => new Promise((resolve) => - { - const image = new Image(); - - image.onload = () => resolve(image); - image.src = resolveURL(url); - }); - - Promise.all([ - loadXML('font.fnt'), - loadXML('font@0.5x.fnt'), - loadImage('font.png'), - loadImage('font@0.5x.png'), - loadImage('atlas.png'), - loadImage('atlas@0.5x.png'), - ]).then(([ - fontXML, - fontScaledXML, - fontImage, - fontScaledImage, - atlasImage, - atlasScaledImage, - ]) => - { - this.fontXML = fontXML; - this.fontScaledXML = fontScaledXML; - this.fontImage = fontImage; - this.fontScaledImage = fontScaledImage; - this.atlasImage = atlasImage; - this.atlasScaledImage = atlasScaledImage; - done(); - }); - }); - - it('should exist and return a function', function () - { - expect(bitmapFontParser).to.be.a('function'); - expect(bitmapFontParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not XML', function () - { - const spy = sinon.spy(); - const res = {}; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is not properly formatted XML', function () - { - const spy = sinon.spy(); - const res = { data: document.createDocumentFragment() }; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - // TODO: Test the texture cache code path. - // TODO: Test the loading texture code path. - // TODO: Test data-url code paths. - - it('should properly register bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charA.texture.frame.x).to.equal(2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charB.texture.frame.x).to.equal(2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charC.texture.frame.x).to.equal(23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charD.texture.frame.x).to.equal(19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register SCALED bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); - const font = BitmapText.registerFont(this.fontScaledXML, 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register bitmap font NESTED into spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); - - it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); - - spritesheet.resolution = 1; - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); -}); - -describe('PIXI.loaders.parseBitmapFontData', function () -{ - it('should exist', function () - { - expect(parseBitmapFontData).to.be.a('function'); - }); - - // TODO: Test the parser code. -}); diff --git a/packages/loaders/test/index.js b/packages/loaders/test/index.js index f00de5f..72c6554 100644 --- a/packages/loaders/test/index.js +++ b/packages/loaders/test/index.js @@ -1,4 +1,2 @@ -require('./bitmapFontParser'); require('./loader'); -require('./spritesheetParser'); require('./textureParser'); diff --git a/packages/loaders/test/resources/atlas.json b/packages/loaders/test/resources/atlas.json deleted file mode 100644 index 86e65a5..0000000 --- a/packages/loaders/test/resources/atlas.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas.png b/packages/loaders/test/resources/atlas.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/atlas@0.5x.json b/packages/loaders/test/resources/atlas@0.5x.json deleted file mode 100644 index ae990a1..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas@0.5x.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas@0.5x.png b/packages/loaders/test/resources/atlas@0.5x.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font.fnt b/packages/loaders/test/resources/font.fnt deleted file mode 100644 index 56e1060..0000000 --- a/packages/loaders/test/resources/font.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font.png b/packages/loaders/test/resources/font.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font@0.5x.fnt b/packages/loaders/test/resources/font@0.5x.fnt deleted file mode 100644 index 6c247c7..0000000 --- a/packages/loaders/test/resources/font@0.5x.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/packages/loaders/src/spritesheetParser.js b/packages/loaders/src/spritesheetParser.js deleted file mode 100644 index f7fa206..0000000 --- a/packages/loaders/src/spritesheetParser.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Resource } from 'resource-loader'; -import url from 'url'; -import { Spritesheet } from '@pixi/spritesheet'; - -export default function () -{ - return function spritesheetParser(resource, next) - { - const imageResourceName = `${resource.name}_image`; - - // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists - if (!resource.data - || resource.type !== Resource.TYPE.JSON - || !resource.data.frames - || this.resources[imageResourceName] - ) - { - next(); - - return; - } - - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - const resourcePath = getResourcePath(resource, this.baseUrl); - - // load the image for this sheet - this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) - { - const spritesheet = new Spritesheet( - res.texture.baseTexture, - resource.data, - resource.url - ); - - spritesheet.parse(() => - { - resource.spritesheet = spritesheet; - resource.textures = spritesheet.textures; - next(); - }); - }); - }; -} - -export function getResourcePath(resource, baseUrl) -{ - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - return resource.data.meta.image; - } - - return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); -} diff --git a/packages/loaders/test/bitmapFontParser.js b/packages/loaders/test/bitmapFontParser.js deleted file mode 100644 index 43ef194..0000000 --- a/packages/loaders/test/bitmapFontParser.js +++ /dev/null @@ -1,325 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const { BitmapText } = require('@pixi/text-bitmap'); -const { BaseTextureCache, TextureCache } = require('@pixi/utils'); -const { Texture, BaseTexture } = require('@pixi/core'); -const { Spritesheet } = require('@pixi/spritesheet'); -const { bitmapFontParser, parseBitmapFontData } = require('../'); - -describe('PIXI.loaders.bitmapFontParser', function () -{ - afterEach(function () - { - for (const font in BitmapText.fonts) - { - delete BitmapText.fonts[font]; - } - for (const baseTexture in BaseTextureCache) - { - delete BaseTextureCache[baseTexture]; - } - for (const texture in TextureCache) - { - delete TextureCache[texture]; - } - }); - - before(function (done) - { - const resolveURL = (url) => path.resolve(this.resources, url); - - this.resources = path.join(__dirname, 'resources'); - this.fontXML = null; - this.fontScaledXML = null; - this.fontImage = null; - this.fontScaledImage = null; - this.atlasImage = null; - this.atlasScaledImage = null; - this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require - this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require - - const loadXML = (url) => new Promise((resolve) => - fs.readFile(resolveURL(url), 'utf8', (err, data) => - { - expect(err).to.be.null; - resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); - })); - - const loadImage = (url) => new Promise((resolve) => - { - const image = new Image(); - - image.onload = () => resolve(image); - image.src = resolveURL(url); - }); - - Promise.all([ - loadXML('font.fnt'), - loadXML('font@0.5x.fnt'), - loadImage('font.png'), - loadImage('font@0.5x.png'), - loadImage('atlas.png'), - loadImage('atlas@0.5x.png'), - ]).then(([ - fontXML, - fontScaledXML, - fontImage, - fontScaledImage, - atlasImage, - atlasScaledImage, - ]) => - { - this.fontXML = fontXML; - this.fontScaledXML = fontScaledXML; - this.fontImage = fontImage; - this.fontScaledImage = fontScaledImage; - this.atlasImage = atlasImage; - this.atlasScaledImage = atlasScaledImage; - done(); - }); - }); - - it('should exist and return a function', function () - { - expect(bitmapFontParser).to.be.a('function'); - expect(bitmapFontParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not XML', function () - { - const spy = sinon.spy(); - const res = {}; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is not properly formatted XML', function () - { - const spy = sinon.spy(); - const res = { data: document.createDocumentFragment() }; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - // TODO: Test the texture cache code path. - // TODO: Test the loading texture code path. - // TODO: Test data-url code paths. - - it('should properly register bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charA.texture.frame.x).to.equal(2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charB.texture.frame.x).to.equal(2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charC.texture.frame.x).to.equal(23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charD.texture.frame.x).to.equal(19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register SCALED bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); - const font = BitmapText.registerFont(this.fontScaledXML, 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register bitmap font NESTED into spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); - - it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); - - spritesheet.resolution = 1; - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); -}); - -describe('PIXI.loaders.parseBitmapFontData', function () -{ - it('should exist', function () - { - expect(parseBitmapFontData).to.be.a('function'); - }); - - // TODO: Test the parser code. -}); diff --git a/packages/loaders/test/index.js b/packages/loaders/test/index.js index f00de5f..72c6554 100644 --- a/packages/loaders/test/index.js +++ b/packages/loaders/test/index.js @@ -1,4 +1,2 @@ -require('./bitmapFontParser'); require('./loader'); -require('./spritesheetParser'); require('./textureParser'); diff --git a/packages/loaders/test/resources/atlas.json b/packages/loaders/test/resources/atlas.json deleted file mode 100644 index 86e65a5..0000000 --- a/packages/loaders/test/resources/atlas.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas.png b/packages/loaders/test/resources/atlas.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/atlas@0.5x.json b/packages/loaders/test/resources/atlas@0.5x.json deleted file mode 100644 index ae990a1..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas@0.5x.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas@0.5x.png b/packages/loaders/test/resources/atlas@0.5x.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font.fnt b/packages/loaders/test/resources/font.fnt deleted file mode 100644 index 56e1060..0000000 --- a/packages/loaders/test/resources/font.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font.png b/packages/loaders/test/resources/font.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font@0.5x.fnt b/packages/loaders/test/resources/font@0.5x.fnt deleted file mode 100644 index 6c247c7..0000000 --- a/packages/loaders/test/resources/font@0.5x.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font@0.5x.png b/packages/loaders/test/resources/font@0.5x.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font@0.5x.png +++ /dev/null Binary files differ diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/packages/loaders/src/spritesheetParser.js b/packages/loaders/src/spritesheetParser.js deleted file mode 100644 index f7fa206..0000000 --- a/packages/loaders/src/spritesheetParser.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Resource } from 'resource-loader'; -import url from 'url'; -import { Spritesheet } from '@pixi/spritesheet'; - -export default function () -{ - return function spritesheetParser(resource, next) - { - const imageResourceName = `${resource.name}_image`; - - // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists - if (!resource.data - || resource.type !== Resource.TYPE.JSON - || !resource.data.frames - || this.resources[imageResourceName] - ) - { - next(); - - return; - } - - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - const resourcePath = getResourcePath(resource, this.baseUrl); - - // load the image for this sheet - this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) - { - const spritesheet = new Spritesheet( - res.texture.baseTexture, - resource.data, - resource.url - ); - - spritesheet.parse(() => - { - resource.spritesheet = spritesheet; - resource.textures = spritesheet.textures; - next(); - }); - }); - }; -} - -export function getResourcePath(resource, baseUrl) -{ - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - return resource.data.meta.image; - } - - return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); -} diff --git a/packages/loaders/test/bitmapFontParser.js b/packages/loaders/test/bitmapFontParser.js deleted file mode 100644 index 43ef194..0000000 --- a/packages/loaders/test/bitmapFontParser.js +++ /dev/null @@ -1,325 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const { BitmapText } = require('@pixi/text-bitmap'); -const { BaseTextureCache, TextureCache } = require('@pixi/utils'); -const { Texture, BaseTexture } = require('@pixi/core'); -const { Spritesheet } = require('@pixi/spritesheet'); -const { bitmapFontParser, parseBitmapFontData } = require('../'); - -describe('PIXI.loaders.bitmapFontParser', function () -{ - afterEach(function () - { - for (const font in BitmapText.fonts) - { - delete BitmapText.fonts[font]; - } - for (const baseTexture in BaseTextureCache) - { - delete BaseTextureCache[baseTexture]; - } - for (const texture in TextureCache) - { - delete TextureCache[texture]; - } - }); - - before(function (done) - { - const resolveURL = (url) => path.resolve(this.resources, url); - - this.resources = path.join(__dirname, 'resources'); - this.fontXML = null; - this.fontScaledXML = null; - this.fontImage = null; - this.fontScaledImage = null; - this.atlasImage = null; - this.atlasScaledImage = null; - this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require - this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require - - const loadXML = (url) => new Promise((resolve) => - fs.readFile(resolveURL(url), 'utf8', (err, data) => - { - expect(err).to.be.null; - resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); - })); - - const loadImage = (url) => new Promise((resolve) => - { - const image = new Image(); - - image.onload = () => resolve(image); - image.src = resolveURL(url); - }); - - Promise.all([ - loadXML('font.fnt'), - loadXML('font@0.5x.fnt'), - loadImage('font.png'), - loadImage('font@0.5x.png'), - loadImage('atlas.png'), - loadImage('atlas@0.5x.png'), - ]).then(([ - fontXML, - fontScaledXML, - fontImage, - fontScaledImage, - atlasImage, - atlasScaledImage, - ]) => - { - this.fontXML = fontXML; - this.fontScaledXML = fontScaledXML; - this.fontImage = fontImage; - this.fontScaledImage = fontScaledImage; - this.atlasImage = atlasImage; - this.atlasScaledImage = atlasScaledImage; - done(); - }); - }); - - it('should exist and return a function', function () - { - expect(bitmapFontParser).to.be.a('function'); - expect(bitmapFontParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not XML', function () - { - const spy = sinon.spy(); - const res = {}; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is not properly formatted XML', function () - { - const spy = sinon.spy(); - const res = { data: document.createDocumentFragment() }; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - // TODO: Test the texture cache code path. - // TODO: Test the loading texture code path. - // TODO: Test data-url code paths. - - it('should properly register bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charA.texture.frame.x).to.equal(2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charB.texture.frame.x).to.equal(2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charC.texture.frame.x).to.equal(23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charD.texture.frame.x).to.equal(19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register SCALED bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); - const font = BitmapText.registerFont(this.fontScaledXML, 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register bitmap font NESTED into spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); - - it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); - - spritesheet.resolution = 1; - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); -}); - -describe('PIXI.loaders.parseBitmapFontData', function () -{ - it('should exist', function () - { - expect(parseBitmapFontData).to.be.a('function'); - }); - - // TODO: Test the parser code. -}); diff --git a/packages/loaders/test/index.js b/packages/loaders/test/index.js index f00de5f..72c6554 100644 --- a/packages/loaders/test/index.js +++ b/packages/loaders/test/index.js @@ -1,4 +1,2 @@ -require('./bitmapFontParser'); require('./loader'); -require('./spritesheetParser'); require('./textureParser'); diff --git a/packages/loaders/test/resources/atlas.json b/packages/loaders/test/resources/atlas.json deleted file mode 100644 index 86e65a5..0000000 --- a/packages/loaders/test/resources/atlas.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas.png b/packages/loaders/test/resources/atlas.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/atlas@0.5x.json b/packages/loaders/test/resources/atlas@0.5x.json deleted file mode 100644 index ae990a1..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas@0.5x.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas@0.5x.png b/packages/loaders/test/resources/atlas@0.5x.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font.fnt b/packages/loaders/test/resources/font.fnt deleted file mode 100644 index 56e1060..0000000 --- a/packages/loaders/test/resources/font.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font.png b/packages/loaders/test/resources/font.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font@0.5x.fnt b/packages/loaders/test/resources/font@0.5x.fnt deleted file mode 100644 index 6c247c7..0000000 --- a/packages/loaders/test/resources/font@0.5x.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font@0.5x.png b/packages/loaders/test/resources/font@0.5x.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/spritesheetParser.js b/packages/loaders/test/spritesheetParser.js deleted file mode 100644 index 98890b8..0000000 --- a/packages/loaders/test/spritesheetParser.js +++ /dev/null @@ -1,213 +0,0 @@ -const path = require('path'); -const { spritesheetParser, Resource, Loader, getResourcePath } = require('../'); -const { Texture, BaseTexture } = require('@pixi/core'); - -describe('PIXI.loaders.spritesheetParser', function () -{ - it('should exist and return a function', function () - { - expect(spritesheetParser).to.be.a('function'); - expect(spritesheetParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not JSON', function () - { - const spy = sinon.spy(); - const res = {}; - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is JSON, but improper format', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, {}); - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should load the image & create textures if json is properly formatted', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); - const loader = new Loader(); - const addStub = sinon.stub(loader, 'add'); - const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); - - imgRes.texture = new Texture(new BaseTexture(imgRes.data)); - - addStub.yields(imgRes); - - spritesheetParser().call(loader, res, spy); - - addStub.restore(); - - expect(spy).to.have.been.calledOnce; - expect(addStub).to.have.been.calledWith( - `${res.name}_image`, - `${path.dirname(res.url)}/${res.data.meta.image}` - ); - expect(res).to.have.property('textures') - .that.is.an('object') - .with.keys(Object.keys(getJsonSpritesheet().frames)) - .and.has.property('0.png') - .that.is.an.instanceof(Texture); - }); - - it('should build the image url', function () - { - function getPath(url, image) - { - return getResourcePath({ - url, - data: { meta: { image } }, - }); - } - - let result = getPath('http://some.com/spritesheet.json', 'img.png'); - - expect(result).to.be.equals('http://some.com/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('http://some.com/some/img.png'); - - result = getPath('/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/img.png'); - - result = getPath('/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('/some/img.png'); - }); - - // TODO: Test that rectangles are created correctly. - // TODO: Test that bathc processing works correctly. - // TODO: Test that resolution processing works correctly. - // TODO: Test that metadata is honored. -}); - -function createMockResource(type, data) -{ - const name = `${Math.floor(Date.now() * Math.random())}`; - - return { - url: `http://localhost/doesnt_exist/${name}`, - name, - type, - data, - metadata: {}, - }; -} - -function getJsonSpritesheet() -{ - /* eslint-disable */ - return {"frames": { - "0.png": - { - "frame": {"x":14,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "1.png": - { - "frame": {"x":14,"y":42,"w":12,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, - "sourceSize": {"w":12,"h":14} - }, - "2.png": - { - "frame": {"x":14,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "3.png": - { - "frame": {"x":42,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "4.png": - { - "frame": {"x":28,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "5.png": - { - "frame": {"x":14,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "6.png": - { - "frame": {"x":0,"y":42,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "7.png": - { - "frame": {"x":0,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "8.png": - { - "frame": {"x":0,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "9.png": - { - "frame": {"x":0,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }}, - "meta": { - "app": "http://www.texturepacker.com", - "version": "1.0", - "image": "hud.png", - "format": "RGBA8888", - "size": {"w":64,"h":64}, - "scale": "1", - "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" - } - }; - /* eslint-enable */ -} diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/packages/loaders/src/spritesheetParser.js b/packages/loaders/src/spritesheetParser.js deleted file mode 100644 index f7fa206..0000000 --- a/packages/loaders/src/spritesheetParser.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Resource } from 'resource-loader'; -import url from 'url'; -import { Spritesheet } from '@pixi/spritesheet'; - -export default function () -{ - return function spritesheetParser(resource, next) - { - const imageResourceName = `${resource.name}_image`; - - // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists - if (!resource.data - || resource.type !== Resource.TYPE.JSON - || !resource.data.frames - || this.resources[imageResourceName] - ) - { - next(); - - return; - } - - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - const resourcePath = getResourcePath(resource, this.baseUrl); - - // load the image for this sheet - this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) - { - const spritesheet = new Spritesheet( - res.texture.baseTexture, - resource.data, - resource.url - ); - - spritesheet.parse(() => - { - resource.spritesheet = spritesheet; - resource.textures = spritesheet.textures; - next(); - }); - }); - }; -} - -export function getResourcePath(resource, baseUrl) -{ - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - return resource.data.meta.image; - } - - return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); -} diff --git a/packages/loaders/test/bitmapFontParser.js b/packages/loaders/test/bitmapFontParser.js deleted file mode 100644 index 43ef194..0000000 --- a/packages/loaders/test/bitmapFontParser.js +++ /dev/null @@ -1,325 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const { BitmapText } = require('@pixi/text-bitmap'); -const { BaseTextureCache, TextureCache } = require('@pixi/utils'); -const { Texture, BaseTexture } = require('@pixi/core'); -const { Spritesheet } = require('@pixi/spritesheet'); -const { bitmapFontParser, parseBitmapFontData } = require('../'); - -describe('PIXI.loaders.bitmapFontParser', function () -{ - afterEach(function () - { - for (const font in BitmapText.fonts) - { - delete BitmapText.fonts[font]; - } - for (const baseTexture in BaseTextureCache) - { - delete BaseTextureCache[baseTexture]; - } - for (const texture in TextureCache) - { - delete TextureCache[texture]; - } - }); - - before(function (done) - { - const resolveURL = (url) => path.resolve(this.resources, url); - - this.resources = path.join(__dirname, 'resources'); - this.fontXML = null; - this.fontScaledXML = null; - this.fontImage = null; - this.fontScaledImage = null; - this.atlasImage = null; - this.atlasScaledImage = null; - this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require - this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require - - const loadXML = (url) => new Promise((resolve) => - fs.readFile(resolveURL(url), 'utf8', (err, data) => - { - expect(err).to.be.null; - resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); - })); - - const loadImage = (url) => new Promise((resolve) => - { - const image = new Image(); - - image.onload = () => resolve(image); - image.src = resolveURL(url); - }); - - Promise.all([ - loadXML('font.fnt'), - loadXML('font@0.5x.fnt'), - loadImage('font.png'), - loadImage('font@0.5x.png'), - loadImage('atlas.png'), - loadImage('atlas@0.5x.png'), - ]).then(([ - fontXML, - fontScaledXML, - fontImage, - fontScaledImage, - atlasImage, - atlasScaledImage, - ]) => - { - this.fontXML = fontXML; - this.fontScaledXML = fontScaledXML; - this.fontImage = fontImage; - this.fontScaledImage = fontScaledImage; - this.atlasImage = atlasImage; - this.atlasScaledImage = atlasScaledImage; - done(); - }); - }); - - it('should exist and return a function', function () - { - expect(bitmapFontParser).to.be.a('function'); - expect(bitmapFontParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not XML', function () - { - const spy = sinon.spy(); - const res = {}; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is not properly formatted XML', function () - { - const spy = sinon.spy(); - const res = { data: document.createDocumentFragment() }; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - // TODO: Test the texture cache code path. - // TODO: Test the loading texture code path. - // TODO: Test data-url code paths. - - it('should properly register bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charA.texture.frame.x).to.equal(2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charB.texture.frame.x).to.equal(2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charC.texture.frame.x).to.equal(23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charD.texture.frame.x).to.equal(19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register SCALED bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); - const font = BitmapText.registerFont(this.fontScaledXML, 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register bitmap font NESTED into spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); - - it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); - - spritesheet.resolution = 1; - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); -}); - -describe('PIXI.loaders.parseBitmapFontData', function () -{ - it('should exist', function () - { - expect(parseBitmapFontData).to.be.a('function'); - }); - - // TODO: Test the parser code. -}); diff --git a/packages/loaders/test/index.js b/packages/loaders/test/index.js index f00de5f..72c6554 100644 --- a/packages/loaders/test/index.js +++ b/packages/loaders/test/index.js @@ -1,4 +1,2 @@ -require('./bitmapFontParser'); require('./loader'); -require('./spritesheetParser'); require('./textureParser'); diff --git a/packages/loaders/test/resources/atlas.json b/packages/loaders/test/resources/atlas.json deleted file mode 100644 index 86e65a5..0000000 --- a/packages/loaders/test/resources/atlas.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas.png b/packages/loaders/test/resources/atlas.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/atlas@0.5x.json b/packages/loaders/test/resources/atlas@0.5x.json deleted file mode 100644 index ae990a1..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas@0.5x.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas@0.5x.png b/packages/loaders/test/resources/atlas@0.5x.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font.fnt b/packages/loaders/test/resources/font.fnt deleted file mode 100644 index 56e1060..0000000 --- a/packages/loaders/test/resources/font.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font.png b/packages/loaders/test/resources/font.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font@0.5x.fnt b/packages/loaders/test/resources/font@0.5x.fnt deleted file mode 100644 index 6c247c7..0000000 --- a/packages/loaders/test/resources/font@0.5x.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font@0.5x.png b/packages/loaders/test/resources/font@0.5x.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/spritesheetParser.js b/packages/loaders/test/spritesheetParser.js deleted file mode 100644 index 98890b8..0000000 --- a/packages/loaders/test/spritesheetParser.js +++ /dev/null @@ -1,213 +0,0 @@ -const path = require('path'); -const { spritesheetParser, Resource, Loader, getResourcePath } = require('../'); -const { Texture, BaseTexture } = require('@pixi/core'); - -describe('PIXI.loaders.spritesheetParser', function () -{ - it('should exist and return a function', function () - { - expect(spritesheetParser).to.be.a('function'); - expect(spritesheetParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not JSON', function () - { - const spy = sinon.spy(); - const res = {}; - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is JSON, but improper format', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, {}); - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should load the image & create textures if json is properly formatted', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); - const loader = new Loader(); - const addStub = sinon.stub(loader, 'add'); - const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); - - imgRes.texture = new Texture(new BaseTexture(imgRes.data)); - - addStub.yields(imgRes); - - spritesheetParser().call(loader, res, spy); - - addStub.restore(); - - expect(spy).to.have.been.calledOnce; - expect(addStub).to.have.been.calledWith( - `${res.name}_image`, - `${path.dirname(res.url)}/${res.data.meta.image}` - ); - expect(res).to.have.property('textures') - .that.is.an('object') - .with.keys(Object.keys(getJsonSpritesheet().frames)) - .and.has.property('0.png') - .that.is.an.instanceof(Texture); - }); - - it('should build the image url', function () - { - function getPath(url, image) - { - return getResourcePath({ - url, - data: { meta: { image } }, - }); - } - - let result = getPath('http://some.com/spritesheet.json', 'img.png'); - - expect(result).to.be.equals('http://some.com/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('http://some.com/some/img.png'); - - result = getPath('/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/img.png'); - - result = getPath('/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('/some/img.png'); - }); - - // TODO: Test that rectangles are created correctly. - // TODO: Test that bathc processing works correctly. - // TODO: Test that resolution processing works correctly. - // TODO: Test that metadata is honored. -}); - -function createMockResource(type, data) -{ - const name = `${Math.floor(Date.now() * Math.random())}`; - - return { - url: `http://localhost/doesnt_exist/${name}`, - name, - type, - data, - metadata: {}, - }; -} - -function getJsonSpritesheet() -{ - /* eslint-disable */ - return {"frames": { - "0.png": - { - "frame": {"x":14,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "1.png": - { - "frame": {"x":14,"y":42,"w":12,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, - "sourceSize": {"w":12,"h":14} - }, - "2.png": - { - "frame": {"x":14,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "3.png": - { - "frame": {"x":42,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "4.png": - { - "frame": {"x":28,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "5.png": - { - "frame": {"x":14,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "6.png": - { - "frame": {"x":0,"y":42,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "7.png": - { - "frame": {"x":0,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "8.png": - { - "frame": {"x":0,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "9.png": - { - "frame": {"x":0,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }}, - "meta": { - "app": "http://www.texturepacker.com", - "version": "1.0", - "image": "hud.png", - "format": "RGBA8888", - "size": {"w":64,"h":64}, - "scale": "1", - "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" - } - }; - /* eslint-enable */ -} diff --git a/packages/mixin-app-loader/LICENSE b/packages/mixin-app-loader/LICENSE new file mode 100644 index 0000000..5d16b50 --- /dev/null +++ b/packages/mixin-app-loader/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2013-2017 Mathew Groves, Chad Engler + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/packages/loaders/src/spritesheetParser.js b/packages/loaders/src/spritesheetParser.js deleted file mode 100644 index f7fa206..0000000 --- a/packages/loaders/src/spritesheetParser.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Resource } from 'resource-loader'; -import url from 'url'; -import { Spritesheet } from '@pixi/spritesheet'; - -export default function () -{ - return function spritesheetParser(resource, next) - { - const imageResourceName = `${resource.name}_image`; - - // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists - if (!resource.data - || resource.type !== Resource.TYPE.JSON - || !resource.data.frames - || this.resources[imageResourceName] - ) - { - next(); - - return; - } - - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - const resourcePath = getResourcePath(resource, this.baseUrl); - - // load the image for this sheet - this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) - { - const spritesheet = new Spritesheet( - res.texture.baseTexture, - resource.data, - resource.url - ); - - spritesheet.parse(() => - { - resource.spritesheet = spritesheet; - resource.textures = spritesheet.textures; - next(); - }); - }); - }; -} - -export function getResourcePath(resource, baseUrl) -{ - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - return resource.data.meta.image; - } - - return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); -} diff --git a/packages/loaders/test/bitmapFontParser.js b/packages/loaders/test/bitmapFontParser.js deleted file mode 100644 index 43ef194..0000000 --- a/packages/loaders/test/bitmapFontParser.js +++ /dev/null @@ -1,325 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const { BitmapText } = require('@pixi/text-bitmap'); -const { BaseTextureCache, TextureCache } = require('@pixi/utils'); -const { Texture, BaseTexture } = require('@pixi/core'); -const { Spritesheet } = require('@pixi/spritesheet'); -const { bitmapFontParser, parseBitmapFontData } = require('../'); - -describe('PIXI.loaders.bitmapFontParser', function () -{ - afterEach(function () - { - for (const font in BitmapText.fonts) - { - delete BitmapText.fonts[font]; - } - for (const baseTexture in BaseTextureCache) - { - delete BaseTextureCache[baseTexture]; - } - for (const texture in TextureCache) - { - delete TextureCache[texture]; - } - }); - - before(function (done) - { - const resolveURL = (url) => path.resolve(this.resources, url); - - this.resources = path.join(__dirname, 'resources'); - this.fontXML = null; - this.fontScaledXML = null; - this.fontImage = null; - this.fontScaledImage = null; - this.atlasImage = null; - this.atlasScaledImage = null; - this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require - this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require - - const loadXML = (url) => new Promise((resolve) => - fs.readFile(resolveURL(url), 'utf8', (err, data) => - { - expect(err).to.be.null; - resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); - })); - - const loadImage = (url) => new Promise((resolve) => - { - const image = new Image(); - - image.onload = () => resolve(image); - image.src = resolveURL(url); - }); - - Promise.all([ - loadXML('font.fnt'), - loadXML('font@0.5x.fnt'), - loadImage('font.png'), - loadImage('font@0.5x.png'), - loadImage('atlas.png'), - loadImage('atlas@0.5x.png'), - ]).then(([ - fontXML, - fontScaledXML, - fontImage, - fontScaledImage, - atlasImage, - atlasScaledImage, - ]) => - { - this.fontXML = fontXML; - this.fontScaledXML = fontScaledXML; - this.fontImage = fontImage; - this.fontScaledImage = fontScaledImage; - this.atlasImage = atlasImage; - this.atlasScaledImage = atlasScaledImage; - done(); - }); - }); - - it('should exist and return a function', function () - { - expect(bitmapFontParser).to.be.a('function'); - expect(bitmapFontParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not XML', function () - { - const spy = sinon.spy(); - const res = {}; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is not properly formatted XML', function () - { - const spy = sinon.spy(); - const res = { data: document.createDocumentFragment() }; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - // TODO: Test the texture cache code path. - // TODO: Test the loading texture code path. - // TODO: Test data-url code paths. - - it('should properly register bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charA.texture.frame.x).to.equal(2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charB.texture.frame.x).to.equal(2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charC.texture.frame.x).to.equal(23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charD.texture.frame.x).to.equal(19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register SCALED bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); - const font = BitmapText.registerFont(this.fontScaledXML, 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register bitmap font NESTED into spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); - - it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); - - spritesheet.resolution = 1; - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); -}); - -describe('PIXI.loaders.parseBitmapFontData', function () -{ - it('should exist', function () - { - expect(parseBitmapFontData).to.be.a('function'); - }); - - // TODO: Test the parser code. -}); diff --git a/packages/loaders/test/index.js b/packages/loaders/test/index.js index f00de5f..72c6554 100644 --- a/packages/loaders/test/index.js +++ b/packages/loaders/test/index.js @@ -1,4 +1,2 @@ -require('./bitmapFontParser'); require('./loader'); -require('./spritesheetParser'); require('./textureParser'); diff --git a/packages/loaders/test/resources/atlas.json b/packages/loaders/test/resources/atlas.json deleted file mode 100644 index 86e65a5..0000000 --- a/packages/loaders/test/resources/atlas.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas.png b/packages/loaders/test/resources/atlas.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/atlas@0.5x.json b/packages/loaders/test/resources/atlas@0.5x.json deleted file mode 100644 index ae990a1..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas@0.5x.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas@0.5x.png b/packages/loaders/test/resources/atlas@0.5x.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font.fnt b/packages/loaders/test/resources/font.fnt deleted file mode 100644 index 56e1060..0000000 --- a/packages/loaders/test/resources/font.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font.png b/packages/loaders/test/resources/font.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font@0.5x.fnt b/packages/loaders/test/resources/font@0.5x.fnt deleted file mode 100644 index 6c247c7..0000000 --- a/packages/loaders/test/resources/font@0.5x.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font@0.5x.png b/packages/loaders/test/resources/font@0.5x.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/spritesheetParser.js b/packages/loaders/test/spritesheetParser.js deleted file mode 100644 index 98890b8..0000000 --- a/packages/loaders/test/spritesheetParser.js +++ /dev/null @@ -1,213 +0,0 @@ -const path = require('path'); -const { spritesheetParser, Resource, Loader, getResourcePath } = require('../'); -const { Texture, BaseTexture } = require('@pixi/core'); - -describe('PIXI.loaders.spritesheetParser', function () -{ - it('should exist and return a function', function () - { - expect(spritesheetParser).to.be.a('function'); - expect(spritesheetParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not JSON', function () - { - const spy = sinon.spy(); - const res = {}; - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is JSON, but improper format', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, {}); - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should load the image & create textures if json is properly formatted', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); - const loader = new Loader(); - const addStub = sinon.stub(loader, 'add'); - const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); - - imgRes.texture = new Texture(new BaseTexture(imgRes.data)); - - addStub.yields(imgRes); - - spritesheetParser().call(loader, res, spy); - - addStub.restore(); - - expect(spy).to.have.been.calledOnce; - expect(addStub).to.have.been.calledWith( - `${res.name}_image`, - `${path.dirname(res.url)}/${res.data.meta.image}` - ); - expect(res).to.have.property('textures') - .that.is.an('object') - .with.keys(Object.keys(getJsonSpritesheet().frames)) - .and.has.property('0.png') - .that.is.an.instanceof(Texture); - }); - - it('should build the image url', function () - { - function getPath(url, image) - { - return getResourcePath({ - url, - data: { meta: { image } }, - }); - } - - let result = getPath('http://some.com/spritesheet.json', 'img.png'); - - expect(result).to.be.equals('http://some.com/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('http://some.com/some/img.png'); - - result = getPath('/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/img.png'); - - result = getPath('/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('/some/img.png'); - }); - - // TODO: Test that rectangles are created correctly. - // TODO: Test that bathc processing works correctly. - // TODO: Test that resolution processing works correctly. - // TODO: Test that metadata is honored. -}); - -function createMockResource(type, data) -{ - const name = `${Math.floor(Date.now() * Math.random())}`; - - return { - url: `http://localhost/doesnt_exist/${name}`, - name, - type, - data, - metadata: {}, - }; -} - -function getJsonSpritesheet() -{ - /* eslint-disable */ - return {"frames": { - "0.png": - { - "frame": {"x":14,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "1.png": - { - "frame": {"x":14,"y":42,"w":12,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, - "sourceSize": {"w":12,"h":14} - }, - "2.png": - { - "frame": {"x":14,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "3.png": - { - "frame": {"x":42,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "4.png": - { - "frame": {"x":28,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "5.png": - { - "frame": {"x":14,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "6.png": - { - "frame": {"x":0,"y":42,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "7.png": - { - "frame": {"x":0,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "8.png": - { - "frame": {"x":0,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "9.png": - { - "frame": {"x":0,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }}, - "meta": { - "app": "http://www.texturepacker.com", - "version": "1.0", - "image": "hud.png", - "format": "RGBA8888", - "size": {"w":64,"h":64}, - "scale": "1", - "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" - } - }; - /* eslint-enable */ -} diff --git a/packages/mixin-app-loader/LICENSE b/packages/mixin-app-loader/LICENSE new file mode 100644 index 0000000..5d16b50 --- /dev/null +++ b/packages/mixin-app-loader/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2013-2017 Mathew Groves, Chad Engler + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/packages/mixin-app-loader/README.md b/packages/mixin-app-loader/README.md new file mode 100644 index 0000000..3e89f86 --- /dev/null +++ b/packages/mixin-app-loader/README.md @@ -0,0 +1,13 @@ +# @pixi/mixin-app-loader + +## Installation + +```bash +npm install @pixi/mixin-app-loader +``` + +## Usage + +```js +import '@pixi/mixin-app-loader'; +``` \ No newline at end of file diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/packages/loaders/src/spritesheetParser.js b/packages/loaders/src/spritesheetParser.js deleted file mode 100644 index f7fa206..0000000 --- a/packages/loaders/src/spritesheetParser.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Resource } from 'resource-loader'; -import url from 'url'; -import { Spritesheet } from '@pixi/spritesheet'; - -export default function () -{ - return function spritesheetParser(resource, next) - { - const imageResourceName = `${resource.name}_image`; - - // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists - if (!resource.data - || resource.type !== Resource.TYPE.JSON - || !resource.data.frames - || this.resources[imageResourceName] - ) - { - next(); - - return; - } - - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - const resourcePath = getResourcePath(resource, this.baseUrl); - - // load the image for this sheet - this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) - { - const spritesheet = new Spritesheet( - res.texture.baseTexture, - resource.data, - resource.url - ); - - spritesheet.parse(() => - { - resource.spritesheet = spritesheet; - resource.textures = spritesheet.textures; - next(); - }); - }); - }; -} - -export function getResourcePath(resource, baseUrl) -{ - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - return resource.data.meta.image; - } - - return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); -} diff --git a/packages/loaders/test/bitmapFontParser.js b/packages/loaders/test/bitmapFontParser.js deleted file mode 100644 index 43ef194..0000000 --- a/packages/loaders/test/bitmapFontParser.js +++ /dev/null @@ -1,325 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const { BitmapText } = require('@pixi/text-bitmap'); -const { BaseTextureCache, TextureCache } = require('@pixi/utils'); -const { Texture, BaseTexture } = require('@pixi/core'); -const { Spritesheet } = require('@pixi/spritesheet'); -const { bitmapFontParser, parseBitmapFontData } = require('../'); - -describe('PIXI.loaders.bitmapFontParser', function () -{ - afterEach(function () - { - for (const font in BitmapText.fonts) - { - delete BitmapText.fonts[font]; - } - for (const baseTexture in BaseTextureCache) - { - delete BaseTextureCache[baseTexture]; - } - for (const texture in TextureCache) - { - delete TextureCache[texture]; - } - }); - - before(function (done) - { - const resolveURL = (url) => path.resolve(this.resources, url); - - this.resources = path.join(__dirname, 'resources'); - this.fontXML = null; - this.fontScaledXML = null; - this.fontImage = null; - this.fontScaledImage = null; - this.atlasImage = null; - this.atlasScaledImage = null; - this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require - this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require - - const loadXML = (url) => new Promise((resolve) => - fs.readFile(resolveURL(url), 'utf8', (err, data) => - { - expect(err).to.be.null; - resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); - })); - - const loadImage = (url) => new Promise((resolve) => - { - const image = new Image(); - - image.onload = () => resolve(image); - image.src = resolveURL(url); - }); - - Promise.all([ - loadXML('font.fnt'), - loadXML('font@0.5x.fnt'), - loadImage('font.png'), - loadImage('font@0.5x.png'), - loadImage('atlas.png'), - loadImage('atlas@0.5x.png'), - ]).then(([ - fontXML, - fontScaledXML, - fontImage, - fontScaledImage, - atlasImage, - atlasScaledImage, - ]) => - { - this.fontXML = fontXML; - this.fontScaledXML = fontScaledXML; - this.fontImage = fontImage; - this.fontScaledImage = fontScaledImage; - this.atlasImage = atlasImage; - this.atlasScaledImage = atlasScaledImage; - done(); - }); - }); - - it('should exist and return a function', function () - { - expect(bitmapFontParser).to.be.a('function'); - expect(bitmapFontParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not XML', function () - { - const spy = sinon.spy(); - const res = {}; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is not properly formatted XML', function () - { - const spy = sinon.spy(); - const res = { data: document.createDocumentFragment() }; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - // TODO: Test the texture cache code path. - // TODO: Test the loading texture code path. - // TODO: Test data-url code paths. - - it('should properly register bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charA.texture.frame.x).to.equal(2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charB.texture.frame.x).to.equal(2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charC.texture.frame.x).to.equal(23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charD.texture.frame.x).to.equal(19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register SCALED bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); - const font = BitmapText.registerFont(this.fontScaledXML, 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register bitmap font NESTED into spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); - - it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); - - spritesheet.resolution = 1; - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); -}); - -describe('PIXI.loaders.parseBitmapFontData', function () -{ - it('should exist', function () - { - expect(parseBitmapFontData).to.be.a('function'); - }); - - // TODO: Test the parser code. -}); diff --git a/packages/loaders/test/index.js b/packages/loaders/test/index.js index f00de5f..72c6554 100644 --- a/packages/loaders/test/index.js +++ b/packages/loaders/test/index.js @@ -1,4 +1,2 @@ -require('./bitmapFontParser'); require('./loader'); -require('./spritesheetParser'); require('./textureParser'); diff --git a/packages/loaders/test/resources/atlas.json b/packages/loaders/test/resources/atlas.json deleted file mode 100644 index 86e65a5..0000000 --- a/packages/loaders/test/resources/atlas.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas.png b/packages/loaders/test/resources/atlas.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/atlas@0.5x.json b/packages/loaders/test/resources/atlas@0.5x.json deleted file mode 100644 index ae990a1..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas@0.5x.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas@0.5x.png b/packages/loaders/test/resources/atlas@0.5x.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font.fnt b/packages/loaders/test/resources/font.fnt deleted file mode 100644 index 56e1060..0000000 --- a/packages/loaders/test/resources/font.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font.png b/packages/loaders/test/resources/font.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font@0.5x.fnt b/packages/loaders/test/resources/font@0.5x.fnt deleted file mode 100644 index 6c247c7..0000000 --- a/packages/loaders/test/resources/font@0.5x.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font@0.5x.png b/packages/loaders/test/resources/font@0.5x.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/spritesheetParser.js b/packages/loaders/test/spritesheetParser.js deleted file mode 100644 index 98890b8..0000000 --- a/packages/loaders/test/spritesheetParser.js +++ /dev/null @@ -1,213 +0,0 @@ -const path = require('path'); -const { spritesheetParser, Resource, Loader, getResourcePath } = require('../'); -const { Texture, BaseTexture } = require('@pixi/core'); - -describe('PIXI.loaders.spritesheetParser', function () -{ - it('should exist and return a function', function () - { - expect(spritesheetParser).to.be.a('function'); - expect(spritesheetParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not JSON', function () - { - const spy = sinon.spy(); - const res = {}; - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is JSON, but improper format', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, {}); - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should load the image & create textures if json is properly formatted', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); - const loader = new Loader(); - const addStub = sinon.stub(loader, 'add'); - const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); - - imgRes.texture = new Texture(new BaseTexture(imgRes.data)); - - addStub.yields(imgRes); - - spritesheetParser().call(loader, res, spy); - - addStub.restore(); - - expect(spy).to.have.been.calledOnce; - expect(addStub).to.have.been.calledWith( - `${res.name}_image`, - `${path.dirname(res.url)}/${res.data.meta.image}` - ); - expect(res).to.have.property('textures') - .that.is.an('object') - .with.keys(Object.keys(getJsonSpritesheet().frames)) - .and.has.property('0.png') - .that.is.an.instanceof(Texture); - }); - - it('should build the image url', function () - { - function getPath(url, image) - { - return getResourcePath({ - url, - data: { meta: { image } }, - }); - } - - let result = getPath('http://some.com/spritesheet.json', 'img.png'); - - expect(result).to.be.equals('http://some.com/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('http://some.com/some/img.png'); - - result = getPath('/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/img.png'); - - result = getPath('/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('/some/img.png'); - }); - - // TODO: Test that rectangles are created correctly. - // TODO: Test that bathc processing works correctly. - // TODO: Test that resolution processing works correctly. - // TODO: Test that metadata is honored. -}); - -function createMockResource(type, data) -{ - const name = `${Math.floor(Date.now() * Math.random())}`; - - return { - url: `http://localhost/doesnt_exist/${name}`, - name, - type, - data, - metadata: {}, - }; -} - -function getJsonSpritesheet() -{ - /* eslint-disable */ - return {"frames": { - "0.png": - { - "frame": {"x":14,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "1.png": - { - "frame": {"x":14,"y":42,"w":12,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, - "sourceSize": {"w":12,"h":14} - }, - "2.png": - { - "frame": {"x":14,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "3.png": - { - "frame": {"x":42,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "4.png": - { - "frame": {"x":28,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "5.png": - { - "frame": {"x":14,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "6.png": - { - "frame": {"x":0,"y":42,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "7.png": - { - "frame": {"x":0,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "8.png": - { - "frame": {"x":0,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "9.png": - { - "frame": {"x":0,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }}, - "meta": { - "app": "http://www.texturepacker.com", - "version": "1.0", - "image": "hud.png", - "format": "RGBA8888", - "size": {"w":64,"h":64}, - "scale": "1", - "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" - } - }; - /* eslint-enable */ -} diff --git a/packages/mixin-app-loader/LICENSE b/packages/mixin-app-loader/LICENSE new file mode 100644 index 0000000..5d16b50 --- /dev/null +++ b/packages/mixin-app-loader/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2013-2017 Mathew Groves, Chad Engler + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/packages/mixin-app-loader/README.md b/packages/mixin-app-loader/README.md new file mode 100644 index 0000000..3e89f86 --- /dev/null +++ b/packages/mixin-app-loader/README.md @@ -0,0 +1,13 @@ +# @pixi/mixin-app-loader + +## Installation + +```bash +npm install @pixi/mixin-app-loader +``` + +## Usage + +```js +import '@pixi/mixin-app-loader'; +``` \ No newline at end of file diff --git a/packages/mixin-app-loader/package.json b/packages/mixin-app-loader/package.json new file mode 100644 index 0000000..869ed4e --- /dev/null +++ b/packages/mixin-app-loader/package.json @@ -0,0 +1,41 @@ +{ + "name": "@pixi/mixin-app-loader", + "version": "5.0.0-alpha", + "main": "lib/mixin-app-loader.js", + "module": "lib/mixin-app-loader.es.js", + "description": "Support for loader in Application", + "author": "Mat Groves", + "contributors": [ + "Matt Karl " + ], + "homepage": "http://pixijs.com/", + "bugs": "https://github.com/pixijs/pixi.js/issues", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/pixijs/pixi.js.git" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "build:dev": "rollup -c", + "build": "rollup -cp", + "watch": "rollup -cw", + "postversion": "npm run build", + "test": "tester" + }, + "files": [ + "lib" + ], + "dependencies": { + "@pixi/app": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" + }, + "devDependencies": { + "@pixi/utils": "^5.0.0-alpha", + "@internal/tester": "^5.0.0-alpha", + "@internal/builder": "^5.0.0-alpha", + "rollup": "^0.50.0" + } +} \ No newline at end of file diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/packages/loaders/src/spritesheetParser.js b/packages/loaders/src/spritesheetParser.js deleted file mode 100644 index f7fa206..0000000 --- a/packages/loaders/src/spritesheetParser.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Resource } from 'resource-loader'; -import url from 'url'; -import { Spritesheet } from '@pixi/spritesheet'; - -export default function () -{ - return function spritesheetParser(resource, next) - { - const imageResourceName = `${resource.name}_image`; - - // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists - if (!resource.data - || resource.type !== Resource.TYPE.JSON - || !resource.data.frames - || this.resources[imageResourceName] - ) - { - next(); - - return; - } - - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - const resourcePath = getResourcePath(resource, this.baseUrl); - - // load the image for this sheet - this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) - { - const spritesheet = new Spritesheet( - res.texture.baseTexture, - resource.data, - resource.url - ); - - spritesheet.parse(() => - { - resource.spritesheet = spritesheet; - resource.textures = spritesheet.textures; - next(); - }); - }); - }; -} - -export function getResourcePath(resource, baseUrl) -{ - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - return resource.data.meta.image; - } - - return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); -} diff --git a/packages/loaders/test/bitmapFontParser.js b/packages/loaders/test/bitmapFontParser.js deleted file mode 100644 index 43ef194..0000000 --- a/packages/loaders/test/bitmapFontParser.js +++ /dev/null @@ -1,325 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const { BitmapText } = require('@pixi/text-bitmap'); -const { BaseTextureCache, TextureCache } = require('@pixi/utils'); -const { Texture, BaseTexture } = require('@pixi/core'); -const { Spritesheet } = require('@pixi/spritesheet'); -const { bitmapFontParser, parseBitmapFontData } = require('../'); - -describe('PIXI.loaders.bitmapFontParser', function () -{ - afterEach(function () - { - for (const font in BitmapText.fonts) - { - delete BitmapText.fonts[font]; - } - for (const baseTexture in BaseTextureCache) - { - delete BaseTextureCache[baseTexture]; - } - for (const texture in TextureCache) - { - delete TextureCache[texture]; - } - }); - - before(function (done) - { - const resolveURL = (url) => path.resolve(this.resources, url); - - this.resources = path.join(__dirname, 'resources'); - this.fontXML = null; - this.fontScaledXML = null; - this.fontImage = null; - this.fontScaledImage = null; - this.atlasImage = null; - this.atlasScaledImage = null; - this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require - this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require - - const loadXML = (url) => new Promise((resolve) => - fs.readFile(resolveURL(url), 'utf8', (err, data) => - { - expect(err).to.be.null; - resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); - })); - - const loadImage = (url) => new Promise((resolve) => - { - const image = new Image(); - - image.onload = () => resolve(image); - image.src = resolveURL(url); - }); - - Promise.all([ - loadXML('font.fnt'), - loadXML('font@0.5x.fnt'), - loadImage('font.png'), - loadImage('font@0.5x.png'), - loadImage('atlas.png'), - loadImage('atlas@0.5x.png'), - ]).then(([ - fontXML, - fontScaledXML, - fontImage, - fontScaledImage, - atlasImage, - atlasScaledImage, - ]) => - { - this.fontXML = fontXML; - this.fontScaledXML = fontScaledXML; - this.fontImage = fontImage; - this.fontScaledImage = fontScaledImage; - this.atlasImage = atlasImage; - this.atlasScaledImage = atlasScaledImage; - done(); - }); - }); - - it('should exist and return a function', function () - { - expect(bitmapFontParser).to.be.a('function'); - expect(bitmapFontParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not XML', function () - { - const spy = sinon.spy(); - const res = {}; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is not properly formatted XML', function () - { - const spy = sinon.spy(); - const res = { data: document.createDocumentFragment() }; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - // TODO: Test the texture cache code path. - // TODO: Test the loading texture code path. - // TODO: Test data-url code paths. - - it('should properly register bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charA.texture.frame.x).to.equal(2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charB.texture.frame.x).to.equal(2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charC.texture.frame.x).to.equal(23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charD.texture.frame.x).to.equal(19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register SCALED bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); - const font = BitmapText.registerFont(this.fontScaledXML, 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register bitmap font NESTED into spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); - - it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); - - spritesheet.resolution = 1; - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); -}); - -describe('PIXI.loaders.parseBitmapFontData', function () -{ - it('should exist', function () - { - expect(parseBitmapFontData).to.be.a('function'); - }); - - // TODO: Test the parser code. -}); diff --git a/packages/loaders/test/index.js b/packages/loaders/test/index.js index f00de5f..72c6554 100644 --- a/packages/loaders/test/index.js +++ b/packages/loaders/test/index.js @@ -1,4 +1,2 @@ -require('./bitmapFontParser'); require('./loader'); -require('./spritesheetParser'); require('./textureParser'); diff --git a/packages/loaders/test/resources/atlas.json b/packages/loaders/test/resources/atlas.json deleted file mode 100644 index 86e65a5..0000000 --- a/packages/loaders/test/resources/atlas.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas.png b/packages/loaders/test/resources/atlas.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/atlas@0.5x.json b/packages/loaders/test/resources/atlas@0.5x.json deleted file mode 100644 index ae990a1..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas@0.5x.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas@0.5x.png b/packages/loaders/test/resources/atlas@0.5x.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font.fnt b/packages/loaders/test/resources/font.fnt deleted file mode 100644 index 56e1060..0000000 --- a/packages/loaders/test/resources/font.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font.png b/packages/loaders/test/resources/font.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font@0.5x.fnt b/packages/loaders/test/resources/font@0.5x.fnt deleted file mode 100644 index 6c247c7..0000000 --- a/packages/loaders/test/resources/font@0.5x.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font@0.5x.png b/packages/loaders/test/resources/font@0.5x.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/spritesheetParser.js b/packages/loaders/test/spritesheetParser.js deleted file mode 100644 index 98890b8..0000000 --- a/packages/loaders/test/spritesheetParser.js +++ /dev/null @@ -1,213 +0,0 @@ -const path = require('path'); -const { spritesheetParser, Resource, Loader, getResourcePath } = require('../'); -const { Texture, BaseTexture } = require('@pixi/core'); - -describe('PIXI.loaders.spritesheetParser', function () -{ - it('should exist and return a function', function () - { - expect(spritesheetParser).to.be.a('function'); - expect(spritesheetParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not JSON', function () - { - const spy = sinon.spy(); - const res = {}; - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is JSON, but improper format', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, {}); - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should load the image & create textures if json is properly formatted', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); - const loader = new Loader(); - const addStub = sinon.stub(loader, 'add'); - const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); - - imgRes.texture = new Texture(new BaseTexture(imgRes.data)); - - addStub.yields(imgRes); - - spritesheetParser().call(loader, res, spy); - - addStub.restore(); - - expect(spy).to.have.been.calledOnce; - expect(addStub).to.have.been.calledWith( - `${res.name}_image`, - `${path.dirname(res.url)}/${res.data.meta.image}` - ); - expect(res).to.have.property('textures') - .that.is.an('object') - .with.keys(Object.keys(getJsonSpritesheet().frames)) - .and.has.property('0.png') - .that.is.an.instanceof(Texture); - }); - - it('should build the image url', function () - { - function getPath(url, image) - { - return getResourcePath({ - url, - data: { meta: { image } }, - }); - } - - let result = getPath('http://some.com/spritesheet.json', 'img.png'); - - expect(result).to.be.equals('http://some.com/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('http://some.com/some/img.png'); - - result = getPath('/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/img.png'); - - result = getPath('/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('/some/img.png'); - }); - - // TODO: Test that rectangles are created correctly. - // TODO: Test that bathc processing works correctly. - // TODO: Test that resolution processing works correctly. - // TODO: Test that metadata is honored. -}); - -function createMockResource(type, data) -{ - const name = `${Math.floor(Date.now() * Math.random())}`; - - return { - url: `http://localhost/doesnt_exist/${name}`, - name, - type, - data, - metadata: {}, - }; -} - -function getJsonSpritesheet() -{ - /* eslint-disable */ - return {"frames": { - "0.png": - { - "frame": {"x":14,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "1.png": - { - "frame": {"x":14,"y":42,"w":12,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, - "sourceSize": {"w":12,"h":14} - }, - "2.png": - { - "frame": {"x":14,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "3.png": - { - "frame": {"x":42,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "4.png": - { - "frame": {"x":28,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "5.png": - { - "frame": {"x":14,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "6.png": - { - "frame": {"x":0,"y":42,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "7.png": - { - "frame": {"x":0,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "8.png": - { - "frame": {"x":0,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "9.png": - { - "frame": {"x":0,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }}, - "meta": { - "app": "http://www.texturepacker.com", - "version": "1.0", - "image": "hud.png", - "format": "RGBA8888", - "size": {"w":64,"h":64}, - "scale": "1", - "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" - } - }; - /* eslint-enable */ -} diff --git a/packages/mixin-app-loader/LICENSE b/packages/mixin-app-loader/LICENSE new file mode 100644 index 0000000..5d16b50 --- /dev/null +++ b/packages/mixin-app-loader/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2013-2017 Mathew Groves, Chad Engler + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/packages/mixin-app-loader/README.md b/packages/mixin-app-loader/README.md new file mode 100644 index 0000000..3e89f86 --- /dev/null +++ b/packages/mixin-app-loader/README.md @@ -0,0 +1,13 @@ +# @pixi/mixin-app-loader + +## Installation + +```bash +npm install @pixi/mixin-app-loader +``` + +## Usage + +```js +import '@pixi/mixin-app-loader'; +``` \ No newline at end of file diff --git a/packages/mixin-app-loader/package.json b/packages/mixin-app-loader/package.json new file mode 100644 index 0000000..869ed4e --- /dev/null +++ b/packages/mixin-app-loader/package.json @@ -0,0 +1,41 @@ +{ + "name": "@pixi/mixin-app-loader", + "version": "5.0.0-alpha", + "main": "lib/mixin-app-loader.js", + "module": "lib/mixin-app-loader.es.js", + "description": "Support for loader in Application", + "author": "Mat Groves", + "contributors": [ + "Matt Karl " + ], + "homepage": "http://pixijs.com/", + "bugs": "https://github.com/pixijs/pixi.js/issues", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/pixijs/pixi.js.git" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "build:dev": "rollup -c", + "build": "rollup -cp", + "watch": "rollup -cw", + "postversion": "npm run build", + "test": "tester" + }, + "files": [ + "lib" + ], + "dependencies": { + "@pixi/app": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" + }, + "devDependencies": { + "@pixi/utils": "^5.0.0-alpha", + "@internal/tester": "^5.0.0-alpha", + "@internal/builder": "^5.0.0-alpha", + "rollup": "^0.50.0" + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/rollup.config.js b/packages/mixin-app-loader/rollup.config.js new file mode 100644 index 0000000..a73c874 --- /dev/null +++ b/packages/mixin-app-loader/rollup.config.js @@ -0,0 +1 @@ +export { default } from '@internal/builder'; diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/packages/loaders/src/spritesheetParser.js b/packages/loaders/src/spritesheetParser.js deleted file mode 100644 index f7fa206..0000000 --- a/packages/loaders/src/spritesheetParser.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Resource } from 'resource-loader'; -import url from 'url'; -import { Spritesheet } from '@pixi/spritesheet'; - -export default function () -{ - return function spritesheetParser(resource, next) - { - const imageResourceName = `${resource.name}_image`; - - // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists - if (!resource.data - || resource.type !== Resource.TYPE.JSON - || !resource.data.frames - || this.resources[imageResourceName] - ) - { - next(); - - return; - } - - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - const resourcePath = getResourcePath(resource, this.baseUrl); - - // load the image for this sheet - this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) - { - const spritesheet = new Spritesheet( - res.texture.baseTexture, - resource.data, - resource.url - ); - - spritesheet.parse(() => - { - resource.spritesheet = spritesheet; - resource.textures = spritesheet.textures; - next(); - }); - }); - }; -} - -export function getResourcePath(resource, baseUrl) -{ - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - return resource.data.meta.image; - } - - return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); -} diff --git a/packages/loaders/test/bitmapFontParser.js b/packages/loaders/test/bitmapFontParser.js deleted file mode 100644 index 43ef194..0000000 --- a/packages/loaders/test/bitmapFontParser.js +++ /dev/null @@ -1,325 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const { BitmapText } = require('@pixi/text-bitmap'); -const { BaseTextureCache, TextureCache } = require('@pixi/utils'); -const { Texture, BaseTexture } = require('@pixi/core'); -const { Spritesheet } = require('@pixi/spritesheet'); -const { bitmapFontParser, parseBitmapFontData } = require('../'); - -describe('PIXI.loaders.bitmapFontParser', function () -{ - afterEach(function () - { - for (const font in BitmapText.fonts) - { - delete BitmapText.fonts[font]; - } - for (const baseTexture in BaseTextureCache) - { - delete BaseTextureCache[baseTexture]; - } - for (const texture in TextureCache) - { - delete TextureCache[texture]; - } - }); - - before(function (done) - { - const resolveURL = (url) => path.resolve(this.resources, url); - - this.resources = path.join(__dirname, 'resources'); - this.fontXML = null; - this.fontScaledXML = null; - this.fontImage = null; - this.fontScaledImage = null; - this.atlasImage = null; - this.atlasScaledImage = null; - this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require - this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require - - const loadXML = (url) => new Promise((resolve) => - fs.readFile(resolveURL(url), 'utf8', (err, data) => - { - expect(err).to.be.null; - resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); - })); - - const loadImage = (url) => new Promise((resolve) => - { - const image = new Image(); - - image.onload = () => resolve(image); - image.src = resolveURL(url); - }); - - Promise.all([ - loadXML('font.fnt'), - loadXML('font@0.5x.fnt'), - loadImage('font.png'), - loadImage('font@0.5x.png'), - loadImage('atlas.png'), - loadImage('atlas@0.5x.png'), - ]).then(([ - fontXML, - fontScaledXML, - fontImage, - fontScaledImage, - atlasImage, - atlasScaledImage, - ]) => - { - this.fontXML = fontXML; - this.fontScaledXML = fontScaledXML; - this.fontImage = fontImage; - this.fontScaledImage = fontScaledImage; - this.atlasImage = atlasImage; - this.atlasScaledImage = atlasScaledImage; - done(); - }); - }); - - it('should exist and return a function', function () - { - expect(bitmapFontParser).to.be.a('function'); - expect(bitmapFontParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not XML', function () - { - const spy = sinon.spy(); - const res = {}; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is not properly formatted XML', function () - { - const spy = sinon.spy(); - const res = { data: document.createDocumentFragment() }; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - // TODO: Test the texture cache code path. - // TODO: Test the loading texture code path. - // TODO: Test data-url code paths. - - it('should properly register bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charA.texture.frame.x).to.equal(2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charB.texture.frame.x).to.equal(2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charC.texture.frame.x).to.equal(23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charD.texture.frame.x).to.equal(19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register SCALED bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); - const font = BitmapText.registerFont(this.fontScaledXML, 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register bitmap font NESTED into spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); - - it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); - - spritesheet.resolution = 1; - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); -}); - -describe('PIXI.loaders.parseBitmapFontData', function () -{ - it('should exist', function () - { - expect(parseBitmapFontData).to.be.a('function'); - }); - - // TODO: Test the parser code. -}); diff --git a/packages/loaders/test/index.js b/packages/loaders/test/index.js index f00de5f..72c6554 100644 --- a/packages/loaders/test/index.js +++ b/packages/loaders/test/index.js @@ -1,4 +1,2 @@ -require('./bitmapFontParser'); require('./loader'); -require('./spritesheetParser'); require('./textureParser'); diff --git a/packages/loaders/test/resources/atlas.json b/packages/loaders/test/resources/atlas.json deleted file mode 100644 index 86e65a5..0000000 --- a/packages/loaders/test/resources/atlas.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas.png b/packages/loaders/test/resources/atlas.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/atlas@0.5x.json b/packages/loaders/test/resources/atlas@0.5x.json deleted file mode 100644 index ae990a1..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas@0.5x.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas@0.5x.png b/packages/loaders/test/resources/atlas@0.5x.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font.fnt b/packages/loaders/test/resources/font.fnt deleted file mode 100644 index 56e1060..0000000 --- a/packages/loaders/test/resources/font.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font.png b/packages/loaders/test/resources/font.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font@0.5x.fnt b/packages/loaders/test/resources/font@0.5x.fnt deleted file mode 100644 index 6c247c7..0000000 --- a/packages/loaders/test/resources/font@0.5x.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font@0.5x.png b/packages/loaders/test/resources/font@0.5x.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/spritesheetParser.js b/packages/loaders/test/spritesheetParser.js deleted file mode 100644 index 98890b8..0000000 --- a/packages/loaders/test/spritesheetParser.js +++ /dev/null @@ -1,213 +0,0 @@ -const path = require('path'); -const { spritesheetParser, Resource, Loader, getResourcePath } = require('../'); -const { Texture, BaseTexture } = require('@pixi/core'); - -describe('PIXI.loaders.spritesheetParser', function () -{ - it('should exist and return a function', function () - { - expect(spritesheetParser).to.be.a('function'); - expect(spritesheetParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not JSON', function () - { - const spy = sinon.spy(); - const res = {}; - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is JSON, but improper format', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, {}); - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should load the image & create textures if json is properly formatted', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); - const loader = new Loader(); - const addStub = sinon.stub(loader, 'add'); - const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); - - imgRes.texture = new Texture(new BaseTexture(imgRes.data)); - - addStub.yields(imgRes); - - spritesheetParser().call(loader, res, spy); - - addStub.restore(); - - expect(spy).to.have.been.calledOnce; - expect(addStub).to.have.been.calledWith( - `${res.name}_image`, - `${path.dirname(res.url)}/${res.data.meta.image}` - ); - expect(res).to.have.property('textures') - .that.is.an('object') - .with.keys(Object.keys(getJsonSpritesheet().frames)) - .and.has.property('0.png') - .that.is.an.instanceof(Texture); - }); - - it('should build the image url', function () - { - function getPath(url, image) - { - return getResourcePath({ - url, - data: { meta: { image } }, - }); - } - - let result = getPath('http://some.com/spritesheet.json', 'img.png'); - - expect(result).to.be.equals('http://some.com/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('http://some.com/some/img.png'); - - result = getPath('/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/img.png'); - - result = getPath('/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('/some/img.png'); - }); - - // TODO: Test that rectangles are created correctly. - // TODO: Test that bathc processing works correctly. - // TODO: Test that resolution processing works correctly. - // TODO: Test that metadata is honored. -}); - -function createMockResource(type, data) -{ - const name = `${Math.floor(Date.now() * Math.random())}`; - - return { - url: `http://localhost/doesnt_exist/${name}`, - name, - type, - data, - metadata: {}, - }; -} - -function getJsonSpritesheet() -{ - /* eslint-disable */ - return {"frames": { - "0.png": - { - "frame": {"x":14,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "1.png": - { - "frame": {"x":14,"y":42,"w":12,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, - "sourceSize": {"w":12,"h":14} - }, - "2.png": - { - "frame": {"x":14,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "3.png": - { - "frame": {"x":42,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "4.png": - { - "frame": {"x":28,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "5.png": - { - "frame": {"x":14,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "6.png": - { - "frame": {"x":0,"y":42,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "7.png": - { - "frame": {"x":0,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "8.png": - { - "frame": {"x":0,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "9.png": - { - "frame": {"x":0,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }}, - "meta": { - "app": "http://www.texturepacker.com", - "version": "1.0", - "image": "hud.png", - "format": "RGBA8888", - "size": {"w":64,"h":64}, - "scale": "1", - "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" - } - }; - /* eslint-enable */ -} diff --git a/packages/mixin-app-loader/LICENSE b/packages/mixin-app-loader/LICENSE new file mode 100644 index 0000000..5d16b50 --- /dev/null +++ b/packages/mixin-app-loader/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2013-2017 Mathew Groves, Chad Engler + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/packages/mixin-app-loader/README.md b/packages/mixin-app-loader/README.md new file mode 100644 index 0000000..3e89f86 --- /dev/null +++ b/packages/mixin-app-loader/README.md @@ -0,0 +1,13 @@ +# @pixi/mixin-app-loader + +## Installation + +```bash +npm install @pixi/mixin-app-loader +``` + +## Usage + +```js +import '@pixi/mixin-app-loader'; +``` \ No newline at end of file diff --git a/packages/mixin-app-loader/package.json b/packages/mixin-app-loader/package.json new file mode 100644 index 0000000..869ed4e --- /dev/null +++ b/packages/mixin-app-loader/package.json @@ -0,0 +1,41 @@ +{ + "name": "@pixi/mixin-app-loader", + "version": "5.0.0-alpha", + "main": "lib/mixin-app-loader.js", + "module": "lib/mixin-app-loader.es.js", + "description": "Support for loader in Application", + "author": "Mat Groves", + "contributors": [ + "Matt Karl " + ], + "homepage": "http://pixijs.com/", + "bugs": "https://github.com/pixijs/pixi.js/issues", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/pixijs/pixi.js.git" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "build:dev": "rollup -c", + "build": "rollup -cp", + "watch": "rollup -cw", + "postversion": "npm run build", + "test": "tester" + }, + "files": [ + "lib" + ], + "dependencies": { + "@pixi/app": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" + }, + "devDependencies": { + "@pixi/utils": "^5.0.0-alpha", + "@internal/tester": "^5.0.0-alpha", + "@internal/builder": "^5.0.0-alpha", + "rollup": "^0.50.0" + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/rollup.config.js b/packages/mixin-app-loader/rollup.config.js new file mode 100644 index 0000000..a73c874 --- /dev/null +++ b/packages/mixin-app-loader/rollup.config.js @@ -0,0 +1 @@ +export { default } from '@internal/builder'; diff --git a/packages/mixin-app-loader/src/index.js b/packages/mixin-app-loader/src/index.js new file mode 100644 index 0000000..354997a --- /dev/null +++ b/packages/mixin-app-loader/src/index.js @@ -0,0 +1,38 @@ +import { Application } from '@pixi/app'; +import { Loader, shared } from '@pixi/loaders'; + +Application.prototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperties(Application.prototype, { + loader: { + get() + { + if (!this._loader && this._options) + { + const { sharedLoader } = this._options; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +Application.prototype._parentDestroy = Application.prototype.destroy; +Application.prototype.destroy = function destroy(removeView) +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(removeView); +}; diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/packages/loaders/src/spritesheetParser.js b/packages/loaders/src/spritesheetParser.js deleted file mode 100644 index f7fa206..0000000 --- a/packages/loaders/src/spritesheetParser.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Resource } from 'resource-loader'; -import url from 'url'; -import { Spritesheet } from '@pixi/spritesheet'; - -export default function () -{ - return function spritesheetParser(resource, next) - { - const imageResourceName = `${resource.name}_image`; - - // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists - if (!resource.data - || resource.type !== Resource.TYPE.JSON - || !resource.data.frames - || this.resources[imageResourceName] - ) - { - next(); - - return; - } - - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - const resourcePath = getResourcePath(resource, this.baseUrl); - - // load the image for this sheet - this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) - { - const spritesheet = new Spritesheet( - res.texture.baseTexture, - resource.data, - resource.url - ); - - spritesheet.parse(() => - { - resource.spritesheet = spritesheet; - resource.textures = spritesheet.textures; - next(); - }); - }); - }; -} - -export function getResourcePath(resource, baseUrl) -{ - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - return resource.data.meta.image; - } - - return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); -} diff --git a/packages/loaders/test/bitmapFontParser.js b/packages/loaders/test/bitmapFontParser.js deleted file mode 100644 index 43ef194..0000000 --- a/packages/loaders/test/bitmapFontParser.js +++ /dev/null @@ -1,325 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const { BitmapText } = require('@pixi/text-bitmap'); -const { BaseTextureCache, TextureCache } = require('@pixi/utils'); -const { Texture, BaseTexture } = require('@pixi/core'); -const { Spritesheet } = require('@pixi/spritesheet'); -const { bitmapFontParser, parseBitmapFontData } = require('../'); - -describe('PIXI.loaders.bitmapFontParser', function () -{ - afterEach(function () - { - for (const font in BitmapText.fonts) - { - delete BitmapText.fonts[font]; - } - for (const baseTexture in BaseTextureCache) - { - delete BaseTextureCache[baseTexture]; - } - for (const texture in TextureCache) - { - delete TextureCache[texture]; - } - }); - - before(function (done) - { - const resolveURL = (url) => path.resolve(this.resources, url); - - this.resources = path.join(__dirname, 'resources'); - this.fontXML = null; - this.fontScaledXML = null; - this.fontImage = null; - this.fontScaledImage = null; - this.atlasImage = null; - this.atlasScaledImage = null; - this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require - this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require - - const loadXML = (url) => new Promise((resolve) => - fs.readFile(resolveURL(url), 'utf8', (err, data) => - { - expect(err).to.be.null; - resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); - })); - - const loadImage = (url) => new Promise((resolve) => - { - const image = new Image(); - - image.onload = () => resolve(image); - image.src = resolveURL(url); - }); - - Promise.all([ - loadXML('font.fnt'), - loadXML('font@0.5x.fnt'), - loadImage('font.png'), - loadImage('font@0.5x.png'), - loadImage('atlas.png'), - loadImage('atlas@0.5x.png'), - ]).then(([ - fontXML, - fontScaledXML, - fontImage, - fontScaledImage, - atlasImage, - atlasScaledImage, - ]) => - { - this.fontXML = fontXML; - this.fontScaledXML = fontScaledXML; - this.fontImage = fontImage; - this.fontScaledImage = fontScaledImage; - this.atlasImage = atlasImage; - this.atlasScaledImage = atlasScaledImage; - done(); - }); - }); - - it('should exist and return a function', function () - { - expect(bitmapFontParser).to.be.a('function'); - expect(bitmapFontParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not XML', function () - { - const spy = sinon.spy(); - const res = {}; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is not properly formatted XML', function () - { - const spy = sinon.spy(); - const res = { data: document.createDocumentFragment() }; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - // TODO: Test the texture cache code path. - // TODO: Test the loading texture code path. - // TODO: Test data-url code paths. - - it('should properly register bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charA.texture.frame.x).to.equal(2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charB.texture.frame.x).to.equal(2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charC.texture.frame.x).to.equal(23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charD.texture.frame.x).to.equal(19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register SCALED bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); - const font = BitmapText.registerFont(this.fontScaledXML, 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register bitmap font NESTED into spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); - - it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); - - spritesheet.resolution = 1; - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); -}); - -describe('PIXI.loaders.parseBitmapFontData', function () -{ - it('should exist', function () - { - expect(parseBitmapFontData).to.be.a('function'); - }); - - // TODO: Test the parser code. -}); diff --git a/packages/loaders/test/index.js b/packages/loaders/test/index.js index f00de5f..72c6554 100644 --- a/packages/loaders/test/index.js +++ b/packages/loaders/test/index.js @@ -1,4 +1,2 @@ -require('./bitmapFontParser'); require('./loader'); -require('./spritesheetParser'); require('./textureParser'); diff --git a/packages/loaders/test/resources/atlas.json b/packages/loaders/test/resources/atlas.json deleted file mode 100644 index 86e65a5..0000000 --- a/packages/loaders/test/resources/atlas.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas.png b/packages/loaders/test/resources/atlas.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/atlas@0.5x.json b/packages/loaders/test/resources/atlas@0.5x.json deleted file mode 100644 index ae990a1..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas@0.5x.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas@0.5x.png b/packages/loaders/test/resources/atlas@0.5x.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font.fnt b/packages/loaders/test/resources/font.fnt deleted file mode 100644 index 56e1060..0000000 --- a/packages/loaders/test/resources/font.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font.png b/packages/loaders/test/resources/font.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font@0.5x.fnt b/packages/loaders/test/resources/font@0.5x.fnt deleted file mode 100644 index 6c247c7..0000000 --- a/packages/loaders/test/resources/font@0.5x.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font@0.5x.png b/packages/loaders/test/resources/font@0.5x.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/spritesheetParser.js b/packages/loaders/test/spritesheetParser.js deleted file mode 100644 index 98890b8..0000000 --- a/packages/loaders/test/spritesheetParser.js +++ /dev/null @@ -1,213 +0,0 @@ -const path = require('path'); -const { spritesheetParser, Resource, Loader, getResourcePath } = require('../'); -const { Texture, BaseTexture } = require('@pixi/core'); - -describe('PIXI.loaders.spritesheetParser', function () -{ - it('should exist and return a function', function () - { - expect(spritesheetParser).to.be.a('function'); - expect(spritesheetParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not JSON', function () - { - const spy = sinon.spy(); - const res = {}; - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is JSON, but improper format', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, {}); - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should load the image & create textures if json is properly formatted', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); - const loader = new Loader(); - const addStub = sinon.stub(loader, 'add'); - const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); - - imgRes.texture = new Texture(new BaseTexture(imgRes.data)); - - addStub.yields(imgRes); - - spritesheetParser().call(loader, res, spy); - - addStub.restore(); - - expect(spy).to.have.been.calledOnce; - expect(addStub).to.have.been.calledWith( - `${res.name}_image`, - `${path.dirname(res.url)}/${res.data.meta.image}` - ); - expect(res).to.have.property('textures') - .that.is.an('object') - .with.keys(Object.keys(getJsonSpritesheet().frames)) - .and.has.property('0.png') - .that.is.an.instanceof(Texture); - }); - - it('should build the image url', function () - { - function getPath(url, image) - { - return getResourcePath({ - url, - data: { meta: { image } }, - }); - } - - let result = getPath('http://some.com/spritesheet.json', 'img.png'); - - expect(result).to.be.equals('http://some.com/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('http://some.com/some/img.png'); - - result = getPath('/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/img.png'); - - result = getPath('/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('/some/img.png'); - }); - - // TODO: Test that rectangles are created correctly. - // TODO: Test that bathc processing works correctly. - // TODO: Test that resolution processing works correctly. - // TODO: Test that metadata is honored. -}); - -function createMockResource(type, data) -{ - const name = `${Math.floor(Date.now() * Math.random())}`; - - return { - url: `http://localhost/doesnt_exist/${name}`, - name, - type, - data, - metadata: {}, - }; -} - -function getJsonSpritesheet() -{ - /* eslint-disable */ - return {"frames": { - "0.png": - { - "frame": {"x":14,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "1.png": - { - "frame": {"x":14,"y":42,"w":12,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, - "sourceSize": {"w":12,"h":14} - }, - "2.png": - { - "frame": {"x":14,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "3.png": - { - "frame": {"x":42,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "4.png": - { - "frame": {"x":28,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "5.png": - { - "frame": {"x":14,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "6.png": - { - "frame": {"x":0,"y":42,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "7.png": - { - "frame": {"x":0,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "8.png": - { - "frame": {"x":0,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "9.png": - { - "frame": {"x":0,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }}, - "meta": { - "app": "http://www.texturepacker.com", - "version": "1.0", - "image": "hud.png", - "format": "RGBA8888", - "size": {"w":64,"h":64}, - "scale": "1", - "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" - } - }; - /* eslint-enable */ -} diff --git a/packages/mixin-app-loader/LICENSE b/packages/mixin-app-loader/LICENSE new file mode 100644 index 0000000..5d16b50 --- /dev/null +++ b/packages/mixin-app-loader/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2013-2017 Mathew Groves, Chad Engler + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/packages/mixin-app-loader/README.md b/packages/mixin-app-loader/README.md new file mode 100644 index 0000000..3e89f86 --- /dev/null +++ b/packages/mixin-app-loader/README.md @@ -0,0 +1,13 @@ +# @pixi/mixin-app-loader + +## Installation + +```bash +npm install @pixi/mixin-app-loader +``` + +## Usage + +```js +import '@pixi/mixin-app-loader'; +``` \ No newline at end of file diff --git a/packages/mixin-app-loader/package.json b/packages/mixin-app-loader/package.json new file mode 100644 index 0000000..869ed4e --- /dev/null +++ b/packages/mixin-app-loader/package.json @@ -0,0 +1,41 @@ +{ + "name": "@pixi/mixin-app-loader", + "version": "5.0.0-alpha", + "main": "lib/mixin-app-loader.js", + "module": "lib/mixin-app-loader.es.js", + "description": "Support for loader in Application", + "author": "Mat Groves", + "contributors": [ + "Matt Karl " + ], + "homepage": "http://pixijs.com/", + "bugs": "https://github.com/pixijs/pixi.js/issues", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/pixijs/pixi.js.git" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "build:dev": "rollup -c", + "build": "rollup -cp", + "watch": "rollup -cw", + "postversion": "npm run build", + "test": "tester" + }, + "files": [ + "lib" + ], + "dependencies": { + "@pixi/app": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" + }, + "devDependencies": { + "@pixi/utils": "^5.0.0-alpha", + "@internal/tester": "^5.0.0-alpha", + "@internal/builder": "^5.0.0-alpha", + "rollup": "^0.50.0" + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/rollup.config.js b/packages/mixin-app-loader/rollup.config.js new file mode 100644 index 0000000..a73c874 --- /dev/null +++ b/packages/mixin-app-loader/rollup.config.js @@ -0,0 +1 @@ +export { default } from '@internal/builder'; diff --git a/packages/mixin-app-loader/src/index.js b/packages/mixin-app-loader/src/index.js new file mode 100644 index 0000000..354997a --- /dev/null +++ b/packages/mixin-app-loader/src/index.js @@ -0,0 +1,38 @@ +import { Application } from '@pixi/app'; +import { Loader, shared } from '@pixi/loaders'; + +Application.prototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperties(Application.prototype, { + loader: { + get() + { + if (!this._loader && this._options) + { + const { sharedLoader } = this._options; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +Application.prototype._parentDestroy = Application.prototype.destroy; +Application.prototype.destroy = function destroy(removeView) +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(removeView); +}; diff --git a/packages/mixin-app-loader/test/.eslintrc.json b/packages/mixin-app-loader/test/.eslintrc.json new file mode 100644 index 0000000..2094b04 --- /dev/null +++ b/packages/mixin-app-loader/test/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "globals": { + "expect": false, + "assert": false, + "sinon": false, + "PIXI": false + }, + "rules": { + "func-names": 0, + "no-unused-expressions": 0 + } +} \ No newline at end of file diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/packages/loaders/src/spritesheetParser.js b/packages/loaders/src/spritesheetParser.js deleted file mode 100644 index f7fa206..0000000 --- a/packages/loaders/src/spritesheetParser.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Resource } from 'resource-loader'; -import url from 'url'; -import { Spritesheet } from '@pixi/spritesheet'; - -export default function () -{ - return function spritesheetParser(resource, next) - { - const imageResourceName = `${resource.name}_image`; - - // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists - if (!resource.data - || resource.type !== Resource.TYPE.JSON - || !resource.data.frames - || this.resources[imageResourceName] - ) - { - next(); - - return; - } - - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - const resourcePath = getResourcePath(resource, this.baseUrl); - - // load the image for this sheet - this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) - { - const spritesheet = new Spritesheet( - res.texture.baseTexture, - resource.data, - resource.url - ); - - spritesheet.parse(() => - { - resource.spritesheet = spritesheet; - resource.textures = spritesheet.textures; - next(); - }); - }); - }; -} - -export function getResourcePath(resource, baseUrl) -{ - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - return resource.data.meta.image; - } - - return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); -} diff --git a/packages/loaders/test/bitmapFontParser.js b/packages/loaders/test/bitmapFontParser.js deleted file mode 100644 index 43ef194..0000000 --- a/packages/loaders/test/bitmapFontParser.js +++ /dev/null @@ -1,325 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const { BitmapText } = require('@pixi/text-bitmap'); -const { BaseTextureCache, TextureCache } = require('@pixi/utils'); -const { Texture, BaseTexture } = require('@pixi/core'); -const { Spritesheet } = require('@pixi/spritesheet'); -const { bitmapFontParser, parseBitmapFontData } = require('../'); - -describe('PIXI.loaders.bitmapFontParser', function () -{ - afterEach(function () - { - for (const font in BitmapText.fonts) - { - delete BitmapText.fonts[font]; - } - for (const baseTexture in BaseTextureCache) - { - delete BaseTextureCache[baseTexture]; - } - for (const texture in TextureCache) - { - delete TextureCache[texture]; - } - }); - - before(function (done) - { - const resolveURL = (url) => path.resolve(this.resources, url); - - this.resources = path.join(__dirname, 'resources'); - this.fontXML = null; - this.fontScaledXML = null; - this.fontImage = null; - this.fontScaledImage = null; - this.atlasImage = null; - this.atlasScaledImage = null; - this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require - this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require - - const loadXML = (url) => new Promise((resolve) => - fs.readFile(resolveURL(url), 'utf8', (err, data) => - { - expect(err).to.be.null; - resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); - })); - - const loadImage = (url) => new Promise((resolve) => - { - const image = new Image(); - - image.onload = () => resolve(image); - image.src = resolveURL(url); - }); - - Promise.all([ - loadXML('font.fnt'), - loadXML('font@0.5x.fnt'), - loadImage('font.png'), - loadImage('font@0.5x.png'), - loadImage('atlas.png'), - loadImage('atlas@0.5x.png'), - ]).then(([ - fontXML, - fontScaledXML, - fontImage, - fontScaledImage, - atlasImage, - atlasScaledImage, - ]) => - { - this.fontXML = fontXML; - this.fontScaledXML = fontScaledXML; - this.fontImage = fontImage; - this.fontScaledImage = fontScaledImage; - this.atlasImage = atlasImage; - this.atlasScaledImage = atlasScaledImage; - done(); - }); - }); - - it('should exist and return a function', function () - { - expect(bitmapFontParser).to.be.a('function'); - expect(bitmapFontParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not XML', function () - { - const spy = sinon.spy(); - const res = {}; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is not properly formatted XML', function () - { - const spy = sinon.spy(); - const res = { data: document.createDocumentFragment() }; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - // TODO: Test the texture cache code path. - // TODO: Test the loading texture code path. - // TODO: Test data-url code paths. - - it('should properly register bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charA.texture.frame.x).to.equal(2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charB.texture.frame.x).to.equal(2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charC.texture.frame.x).to.equal(23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charD.texture.frame.x).to.equal(19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register SCALED bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); - const font = BitmapText.registerFont(this.fontScaledXML, 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register bitmap font NESTED into spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); - - it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); - - spritesheet.resolution = 1; - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); -}); - -describe('PIXI.loaders.parseBitmapFontData', function () -{ - it('should exist', function () - { - expect(parseBitmapFontData).to.be.a('function'); - }); - - // TODO: Test the parser code. -}); diff --git a/packages/loaders/test/index.js b/packages/loaders/test/index.js index f00de5f..72c6554 100644 --- a/packages/loaders/test/index.js +++ b/packages/loaders/test/index.js @@ -1,4 +1,2 @@ -require('./bitmapFontParser'); require('./loader'); -require('./spritesheetParser'); require('./textureParser'); diff --git a/packages/loaders/test/resources/atlas.json b/packages/loaders/test/resources/atlas.json deleted file mode 100644 index 86e65a5..0000000 --- a/packages/loaders/test/resources/atlas.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas.png b/packages/loaders/test/resources/atlas.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/atlas@0.5x.json b/packages/loaders/test/resources/atlas@0.5x.json deleted file mode 100644 index ae990a1..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas@0.5x.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas@0.5x.png b/packages/loaders/test/resources/atlas@0.5x.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font.fnt b/packages/loaders/test/resources/font.fnt deleted file mode 100644 index 56e1060..0000000 --- a/packages/loaders/test/resources/font.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font.png b/packages/loaders/test/resources/font.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font@0.5x.fnt b/packages/loaders/test/resources/font@0.5x.fnt deleted file mode 100644 index 6c247c7..0000000 --- a/packages/loaders/test/resources/font@0.5x.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font@0.5x.png b/packages/loaders/test/resources/font@0.5x.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/spritesheetParser.js b/packages/loaders/test/spritesheetParser.js deleted file mode 100644 index 98890b8..0000000 --- a/packages/loaders/test/spritesheetParser.js +++ /dev/null @@ -1,213 +0,0 @@ -const path = require('path'); -const { spritesheetParser, Resource, Loader, getResourcePath } = require('../'); -const { Texture, BaseTexture } = require('@pixi/core'); - -describe('PIXI.loaders.spritesheetParser', function () -{ - it('should exist and return a function', function () - { - expect(spritesheetParser).to.be.a('function'); - expect(spritesheetParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not JSON', function () - { - const spy = sinon.spy(); - const res = {}; - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is JSON, but improper format', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, {}); - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should load the image & create textures if json is properly formatted', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); - const loader = new Loader(); - const addStub = sinon.stub(loader, 'add'); - const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); - - imgRes.texture = new Texture(new BaseTexture(imgRes.data)); - - addStub.yields(imgRes); - - spritesheetParser().call(loader, res, spy); - - addStub.restore(); - - expect(spy).to.have.been.calledOnce; - expect(addStub).to.have.been.calledWith( - `${res.name}_image`, - `${path.dirname(res.url)}/${res.data.meta.image}` - ); - expect(res).to.have.property('textures') - .that.is.an('object') - .with.keys(Object.keys(getJsonSpritesheet().frames)) - .and.has.property('0.png') - .that.is.an.instanceof(Texture); - }); - - it('should build the image url', function () - { - function getPath(url, image) - { - return getResourcePath({ - url, - data: { meta: { image } }, - }); - } - - let result = getPath('http://some.com/spritesheet.json', 'img.png'); - - expect(result).to.be.equals('http://some.com/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('http://some.com/some/img.png'); - - result = getPath('/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/img.png'); - - result = getPath('/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('/some/img.png'); - }); - - // TODO: Test that rectangles are created correctly. - // TODO: Test that bathc processing works correctly. - // TODO: Test that resolution processing works correctly. - // TODO: Test that metadata is honored. -}); - -function createMockResource(type, data) -{ - const name = `${Math.floor(Date.now() * Math.random())}`; - - return { - url: `http://localhost/doesnt_exist/${name}`, - name, - type, - data, - metadata: {}, - }; -} - -function getJsonSpritesheet() -{ - /* eslint-disable */ - return {"frames": { - "0.png": - { - "frame": {"x":14,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "1.png": - { - "frame": {"x":14,"y":42,"w":12,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, - "sourceSize": {"w":12,"h":14} - }, - "2.png": - { - "frame": {"x":14,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "3.png": - { - "frame": {"x":42,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "4.png": - { - "frame": {"x":28,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "5.png": - { - "frame": {"x":14,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "6.png": - { - "frame": {"x":0,"y":42,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "7.png": - { - "frame": {"x":0,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "8.png": - { - "frame": {"x":0,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "9.png": - { - "frame": {"x":0,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }}, - "meta": { - "app": "http://www.texturepacker.com", - "version": "1.0", - "image": "hud.png", - "format": "RGBA8888", - "size": {"w":64,"h":64}, - "scale": "1", - "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" - } - }; - /* eslint-enable */ -} diff --git a/packages/mixin-app-loader/LICENSE b/packages/mixin-app-loader/LICENSE new file mode 100644 index 0000000..5d16b50 --- /dev/null +++ b/packages/mixin-app-loader/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2013-2017 Mathew Groves, Chad Engler + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/packages/mixin-app-loader/README.md b/packages/mixin-app-loader/README.md new file mode 100644 index 0000000..3e89f86 --- /dev/null +++ b/packages/mixin-app-loader/README.md @@ -0,0 +1,13 @@ +# @pixi/mixin-app-loader + +## Installation + +```bash +npm install @pixi/mixin-app-loader +``` + +## Usage + +```js +import '@pixi/mixin-app-loader'; +``` \ No newline at end of file diff --git a/packages/mixin-app-loader/package.json b/packages/mixin-app-loader/package.json new file mode 100644 index 0000000..869ed4e --- /dev/null +++ b/packages/mixin-app-loader/package.json @@ -0,0 +1,41 @@ +{ + "name": "@pixi/mixin-app-loader", + "version": "5.0.0-alpha", + "main": "lib/mixin-app-loader.js", + "module": "lib/mixin-app-loader.es.js", + "description": "Support for loader in Application", + "author": "Mat Groves", + "contributors": [ + "Matt Karl " + ], + "homepage": "http://pixijs.com/", + "bugs": "https://github.com/pixijs/pixi.js/issues", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/pixijs/pixi.js.git" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "build:dev": "rollup -c", + "build": "rollup -cp", + "watch": "rollup -cw", + "postversion": "npm run build", + "test": "tester" + }, + "files": [ + "lib" + ], + "dependencies": { + "@pixi/app": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" + }, + "devDependencies": { + "@pixi/utils": "^5.0.0-alpha", + "@internal/tester": "^5.0.0-alpha", + "@internal/builder": "^5.0.0-alpha", + "rollup": "^0.50.0" + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/rollup.config.js b/packages/mixin-app-loader/rollup.config.js new file mode 100644 index 0000000..a73c874 --- /dev/null +++ b/packages/mixin-app-loader/rollup.config.js @@ -0,0 +1 @@ +export { default } from '@internal/builder'; diff --git a/packages/mixin-app-loader/src/index.js b/packages/mixin-app-loader/src/index.js new file mode 100644 index 0000000..354997a --- /dev/null +++ b/packages/mixin-app-loader/src/index.js @@ -0,0 +1,38 @@ +import { Application } from '@pixi/app'; +import { Loader, shared } from '@pixi/loaders'; + +Application.prototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperties(Application.prototype, { + loader: { + get() + { + if (!this._loader && this._options) + { + const { sharedLoader } = this._options; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +Application.prototype._parentDestroy = Application.prototype.destroy; +Application.prototype.destroy = function destroy(removeView) +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(removeView); +}; diff --git a/packages/mixin-app-loader/test/.eslintrc.json b/packages/mixin-app-loader/test/.eslintrc.json new file mode 100644 index 0000000..2094b04 --- /dev/null +++ b/packages/mixin-app-loader/test/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "globals": { + "expect": false, + "assert": false, + "sinon": false, + "PIXI": false + }, + "rules": { + "func-names": 0, + "no-unused-expressions": 0 + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/test/index.js b/packages/mixin-app-loader/test/index.js new file mode 100644 index 0000000..3468fe5 --- /dev/null +++ b/packages/mixin-app-loader/test/index.js @@ -0,0 +1,35 @@ +const { Application } = require('@pixi/app'); +const { Loader, shared } = require('@pixi/loaders'); +const { skipHello } = require('@pixi/utils'); + +require('../'); + +skipHello(); + +describe('PIXI.Application#loader', function () +{ + it('should contain loader property', function () + { + const obj = new Application(); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); + + it('should use sharedLoader option', function () + { + const obj = new Application({ sharedLoader: true }); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + expect(obj.loader).to.equal(shared); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); +}); diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/packages/loaders/src/spritesheetParser.js b/packages/loaders/src/spritesheetParser.js deleted file mode 100644 index f7fa206..0000000 --- a/packages/loaders/src/spritesheetParser.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Resource } from 'resource-loader'; -import url from 'url'; -import { Spritesheet } from '@pixi/spritesheet'; - -export default function () -{ - return function spritesheetParser(resource, next) - { - const imageResourceName = `${resource.name}_image`; - - // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists - if (!resource.data - || resource.type !== Resource.TYPE.JSON - || !resource.data.frames - || this.resources[imageResourceName] - ) - { - next(); - - return; - } - - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - const resourcePath = getResourcePath(resource, this.baseUrl); - - // load the image for this sheet - this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) - { - const spritesheet = new Spritesheet( - res.texture.baseTexture, - resource.data, - resource.url - ); - - spritesheet.parse(() => - { - resource.spritesheet = spritesheet; - resource.textures = spritesheet.textures; - next(); - }); - }); - }; -} - -export function getResourcePath(resource, baseUrl) -{ - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - return resource.data.meta.image; - } - - return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); -} diff --git a/packages/loaders/test/bitmapFontParser.js b/packages/loaders/test/bitmapFontParser.js deleted file mode 100644 index 43ef194..0000000 --- a/packages/loaders/test/bitmapFontParser.js +++ /dev/null @@ -1,325 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const { BitmapText } = require('@pixi/text-bitmap'); -const { BaseTextureCache, TextureCache } = require('@pixi/utils'); -const { Texture, BaseTexture } = require('@pixi/core'); -const { Spritesheet } = require('@pixi/spritesheet'); -const { bitmapFontParser, parseBitmapFontData } = require('../'); - -describe('PIXI.loaders.bitmapFontParser', function () -{ - afterEach(function () - { - for (const font in BitmapText.fonts) - { - delete BitmapText.fonts[font]; - } - for (const baseTexture in BaseTextureCache) - { - delete BaseTextureCache[baseTexture]; - } - for (const texture in TextureCache) - { - delete TextureCache[texture]; - } - }); - - before(function (done) - { - const resolveURL = (url) => path.resolve(this.resources, url); - - this.resources = path.join(__dirname, 'resources'); - this.fontXML = null; - this.fontScaledXML = null; - this.fontImage = null; - this.fontScaledImage = null; - this.atlasImage = null; - this.atlasScaledImage = null; - this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require - this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require - - const loadXML = (url) => new Promise((resolve) => - fs.readFile(resolveURL(url), 'utf8', (err, data) => - { - expect(err).to.be.null; - resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); - })); - - const loadImage = (url) => new Promise((resolve) => - { - const image = new Image(); - - image.onload = () => resolve(image); - image.src = resolveURL(url); - }); - - Promise.all([ - loadXML('font.fnt'), - loadXML('font@0.5x.fnt'), - loadImage('font.png'), - loadImage('font@0.5x.png'), - loadImage('atlas.png'), - loadImage('atlas@0.5x.png'), - ]).then(([ - fontXML, - fontScaledXML, - fontImage, - fontScaledImage, - atlasImage, - atlasScaledImage, - ]) => - { - this.fontXML = fontXML; - this.fontScaledXML = fontScaledXML; - this.fontImage = fontImage; - this.fontScaledImage = fontScaledImage; - this.atlasImage = atlasImage; - this.atlasScaledImage = atlasScaledImage; - done(); - }); - }); - - it('should exist and return a function', function () - { - expect(bitmapFontParser).to.be.a('function'); - expect(bitmapFontParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not XML', function () - { - const spy = sinon.spy(); - const res = {}; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is not properly formatted XML', function () - { - const spy = sinon.spy(); - const res = { data: document.createDocumentFragment() }; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - // TODO: Test the texture cache code path. - // TODO: Test the loading texture code path. - // TODO: Test data-url code paths. - - it('should properly register bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charA.texture.frame.x).to.equal(2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charB.texture.frame.x).to.equal(2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charC.texture.frame.x).to.equal(23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charD.texture.frame.x).to.equal(19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register SCALED bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); - const font = BitmapText.registerFont(this.fontScaledXML, 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register bitmap font NESTED into spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); - - it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); - - spritesheet.resolution = 1; - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); -}); - -describe('PIXI.loaders.parseBitmapFontData', function () -{ - it('should exist', function () - { - expect(parseBitmapFontData).to.be.a('function'); - }); - - // TODO: Test the parser code. -}); diff --git a/packages/loaders/test/index.js b/packages/loaders/test/index.js index f00de5f..72c6554 100644 --- a/packages/loaders/test/index.js +++ b/packages/loaders/test/index.js @@ -1,4 +1,2 @@ -require('./bitmapFontParser'); require('./loader'); -require('./spritesheetParser'); require('./textureParser'); diff --git a/packages/loaders/test/resources/atlas.json b/packages/loaders/test/resources/atlas.json deleted file mode 100644 index 86e65a5..0000000 --- a/packages/loaders/test/resources/atlas.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas.png b/packages/loaders/test/resources/atlas.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/atlas@0.5x.json b/packages/loaders/test/resources/atlas@0.5x.json deleted file mode 100644 index ae990a1..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas@0.5x.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas@0.5x.png b/packages/loaders/test/resources/atlas@0.5x.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font.fnt b/packages/loaders/test/resources/font.fnt deleted file mode 100644 index 56e1060..0000000 --- a/packages/loaders/test/resources/font.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font.png b/packages/loaders/test/resources/font.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font@0.5x.fnt b/packages/loaders/test/resources/font@0.5x.fnt deleted file mode 100644 index 6c247c7..0000000 --- a/packages/loaders/test/resources/font@0.5x.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font@0.5x.png b/packages/loaders/test/resources/font@0.5x.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/spritesheetParser.js b/packages/loaders/test/spritesheetParser.js deleted file mode 100644 index 98890b8..0000000 --- a/packages/loaders/test/spritesheetParser.js +++ /dev/null @@ -1,213 +0,0 @@ -const path = require('path'); -const { spritesheetParser, Resource, Loader, getResourcePath } = require('../'); -const { Texture, BaseTexture } = require('@pixi/core'); - -describe('PIXI.loaders.spritesheetParser', function () -{ - it('should exist and return a function', function () - { - expect(spritesheetParser).to.be.a('function'); - expect(spritesheetParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not JSON', function () - { - const spy = sinon.spy(); - const res = {}; - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is JSON, but improper format', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, {}); - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should load the image & create textures if json is properly formatted', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); - const loader = new Loader(); - const addStub = sinon.stub(loader, 'add'); - const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); - - imgRes.texture = new Texture(new BaseTexture(imgRes.data)); - - addStub.yields(imgRes); - - spritesheetParser().call(loader, res, spy); - - addStub.restore(); - - expect(spy).to.have.been.calledOnce; - expect(addStub).to.have.been.calledWith( - `${res.name}_image`, - `${path.dirname(res.url)}/${res.data.meta.image}` - ); - expect(res).to.have.property('textures') - .that.is.an('object') - .with.keys(Object.keys(getJsonSpritesheet().frames)) - .and.has.property('0.png') - .that.is.an.instanceof(Texture); - }); - - it('should build the image url', function () - { - function getPath(url, image) - { - return getResourcePath({ - url, - data: { meta: { image } }, - }); - } - - let result = getPath('http://some.com/spritesheet.json', 'img.png'); - - expect(result).to.be.equals('http://some.com/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('http://some.com/some/img.png'); - - result = getPath('/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/img.png'); - - result = getPath('/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('/some/img.png'); - }); - - // TODO: Test that rectangles are created correctly. - // TODO: Test that bathc processing works correctly. - // TODO: Test that resolution processing works correctly. - // TODO: Test that metadata is honored. -}); - -function createMockResource(type, data) -{ - const name = `${Math.floor(Date.now() * Math.random())}`; - - return { - url: `http://localhost/doesnt_exist/${name}`, - name, - type, - data, - metadata: {}, - }; -} - -function getJsonSpritesheet() -{ - /* eslint-disable */ - return {"frames": { - "0.png": - { - "frame": {"x":14,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "1.png": - { - "frame": {"x":14,"y":42,"w":12,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, - "sourceSize": {"w":12,"h":14} - }, - "2.png": - { - "frame": {"x":14,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "3.png": - { - "frame": {"x":42,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "4.png": - { - "frame": {"x":28,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "5.png": - { - "frame": {"x":14,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "6.png": - { - "frame": {"x":0,"y":42,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "7.png": - { - "frame": {"x":0,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "8.png": - { - "frame": {"x":0,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "9.png": - { - "frame": {"x":0,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }}, - "meta": { - "app": "http://www.texturepacker.com", - "version": "1.0", - "image": "hud.png", - "format": "RGBA8888", - "size": {"w":64,"h":64}, - "scale": "1", - "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" - } - }; - /* eslint-enable */ -} diff --git a/packages/mixin-app-loader/LICENSE b/packages/mixin-app-loader/LICENSE new file mode 100644 index 0000000..5d16b50 --- /dev/null +++ b/packages/mixin-app-loader/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2013-2017 Mathew Groves, Chad Engler + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/packages/mixin-app-loader/README.md b/packages/mixin-app-loader/README.md new file mode 100644 index 0000000..3e89f86 --- /dev/null +++ b/packages/mixin-app-loader/README.md @@ -0,0 +1,13 @@ +# @pixi/mixin-app-loader + +## Installation + +```bash +npm install @pixi/mixin-app-loader +``` + +## Usage + +```js +import '@pixi/mixin-app-loader'; +``` \ No newline at end of file diff --git a/packages/mixin-app-loader/package.json b/packages/mixin-app-loader/package.json new file mode 100644 index 0000000..869ed4e --- /dev/null +++ b/packages/mixin-app-loader/package.json @@ -0,0 +1,41 @@ +{ + "name": "@pixi/mixin-app-loader", + "version": "5.0.0-alpha", + "main": "lib/mixin-app-loader.js", + "module": "lib/mixin-app-loader.es.js", + "description": "Support for loader in Application", + "author": "Mat Groves", + "contributors": [ + "Matt Karl " + ], + "homepage": "http://pixijs.com/", + "bugs": "https://github.com/pixijs/pixi.js/issues", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/pixijs/pixi.js.git" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "build:dev": "rollup -c", + "build": "rollup -cp", + "watch": "rollup -cw", + "postversion": "npm run build", + "test": "tester" + }, + "files": [ + "lib" + ], + "dependencies": { + "@pixi/app": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" + }, + "devDependencies": { + "@pixi/utils": "^5.0.0-alpha", + "@internal/tester": "^5.0.0-alpha", + "@internal/builder": "^5.0.0-alpha", + "rollup": "^0.50.0" + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/rollup.config.js b/packages/mixin-app-loader/rollup.config.js new file mode 100644 index 0000000..a73c874 --- /dev/null +++ b/packages/mixin-app-loader/rollup.config.js @@ -0,0 +1 @@ +export { default } from '@internal/builder'; diff --git a/packages/mixin-app-loader/src/index.js b/packages/mixin-app-loader/src/index.js new file mode 100644 index 0000000..354997a --- /dev/null +++ b/packages/mixin-app-loader/src/index.js @@ -0,0 +1,38 @@ +import { Application } from '@pixi/app'; +import { Loader, shared } from '@pixi/loaders'; + +Application.prototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperties(Application.prototype, { + loader: { + get() + { + if (!this._loader && this._options) + { + const { sharedLoader } = this._options; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +Application.prototype._parentDestroy = Application.prototype.destroy; +Application.prototype.destroy = function destroy(removeView) +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(removeView); +}; diff --git a/packages/mixin-app-loader/test/.eslintrc.json b/packages/mixin-app-loader/test/.eslintrc.json new file mode 100644 index 0000000..2094b04 --- /dev/null +++ b/packages/mixin-app-loader/test/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "globals": { + "expect": false, + "assert": false, + "sinon": false, + "PIXI": false + }, + "rules": { + "func-names": 0, + "no-unused-expressions": 0 + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/test/index.js b/packages/mixin-app-loader/test/index.js new file mode 100644 index 0000000..3468fe5 --- /dev/null +++ b/packages/mixin-app-loader/test/index.js @@ -0,0 +1,35 @@ +const { Application } = require('@pixi/app'); +const { Loader, shared } = require('@pixi/loaders'); +const { skipHello } = require('@pixi/utils'); + +require('../'); + +skipHello(); + +describe('PIXI.Application#loader', function () +{ + it('should contain loader property', function () + { + const obj = new Application(); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); + + it('should use sharedLoader option', function () + { + const obj = new Application({ sharedLoader: true }); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + expect(obj.loader).to.equal(shared); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); +}); diff --git a/packages/spritesheet/package.json b/packages/spritesheet/package.json index 34749e2..5f35442 100644 --- a/packages/spritesheet/package.json +++ b/packages/spritesheet/package.json @@ -31,7 +31,8 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/math": "^5.0.0-alpha", - "@pixi/utils": "^5.0.0-alpha" + "@pixi/utils": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" }, "devDependencies": { "@internal/builder": "^5.0.0-alpha", diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/packages/loaders/src/spritesheetParser.js b/packages/loaders/src/spritesheetParser.js deleted file mode 100644 index f7fa206..0000000 --- a/packages/loaders/src/spritesheetParser.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Resource } from 'resource-loader'; -import url from 'url'; -import { Spritesheet } from '@pixi/spritesheet'; - -export default function () -{ - return function spritesheetParser(resource, next) - { - const imageResourceName = `${resource.name}_image`; - - // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists - if (!resource.data - || resource.type !== Resource.TYPE.JSON - || !resource.data.frames - || this.resources[imageResourceName] - ) - { - next(); - - return; - } - - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - const resourcePath = getResourcePath(resource, this.baseUrl); - - // load the image for this sheet - this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) - { - const spritesheet = new Spritesheet( - res.texture.baseTexture, - resource.data, - resource.url - ); - - spritesheet.parse(() => - { - resource.spritesheet = spritesheet; - resource.textures = spritesheet.textures; - next(); - }); - }); - }; -} - -export function getResourcePath(resource, baseUrl) -{ - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - return resource.data.meta.image; - } - - return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); -} diff --git a/packages/loaders/test/bitmapFontParser.js b/packages/loaders/test/bitmapFontParser.js deleted file mode 100644 index 43ef194..0000000 --- a/packages/loaders/test/bitmapFontParser.js +++ /dev/null @@ -1,325 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const { BitmapText } = require('@pixi/text-bitmap'); -const { BaseTextureCache, TextureCache } = require('@pixi/utils'); -const { Texture, BaseTexture } = require('@pixi/core'); -const { Spritesheet } = require('@pixi/spritesheet'); -const { bitmapFontParser, parseBitmapFontData } = require('../'); - -describe('PIXI.loaders.bitmapFontParser', function () -{ - afterEach(function () - { - for (const font in BitmapText.fonts) - { - delete BitmapText.fonts[font]; - } - for (const baseTexture in BaseTextureCache) - { - delete BaseTextureCache[baseTexture]; - } - for (const texture in TextureCache) - { - delete TextureCache[texture]; - } - }); - - before(function (done) - { - const resolveURL = (url) => path.resolve(this.resources, url); - - this.resources = path.join(__dirname, 'resources'); - this.fontXML = null; - this.fontScaledXML = null; - this.fontImage = null; - this.fontScaledImage = null; - this.atlasImage = null; - this.atlasScaledImage = null; - this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require - this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require - - const loadXML = (url) => new Promise((resolve) => - fs.readFile(resolveURL(url), 'utf8', (err, data) => - { - expect(err).to.be.null; - resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); - })); - - const loadImage = (url) => new Promise((resolve) => - { - const image = new Image(); - - image.onload = () => resolve(image); - image.src = resolveURL(url); - }); - - Promise.all([ - loadXML('font.fnt'), - loadXML('font@0.5x.fnt'), - loadImage('font.png'), - loadImage('font@0.5x.png'), - loadImage('atlas.png'), - loadImage('atlas@0.5x.png'), - ]).then(([ - fontXML, - fontScaledXML, - fontImage, - fontScaledImage, - atlasImage, - atlasScaledImage, - ]) => - { - this.fontXML = fontXML; - this.fontScaledXML = fontScaledXML; - this.fontImage = fontImage; - this.fontScaledImage = fontScaledImage; - this.atlasImage = atlasImage; - this.atlasScaledImage = atlasScaledImage; - done(); - }); - }); - - it('should exist and return a function', function () - { - expect(bitmapFontParser).to.be.a('function'); - expect(bitmapFontParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not XML', function () - { - const spy = sinon.spy(); - const res = {}; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is not properly formatted XML', function () - { - const spy = sinon.spy(); - const res = { data: document.createDocumentFragment() }; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - // TODO: Test the texture cache code path. - // TODO: Test the loading texture code path. - // TODO: Test data-url code paths. - - it('should properly register bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charA.texture.frame.x).to.equal(2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charB.texture.frame.x).to.equal(2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charC.texture.frame.x).to.equal(23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charD.texture.frame.x).to.equal(19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register SCALED bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); - const font = BitmapText.registerFont(this.fontScaledXML, 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register bitmap font NESTED into spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); - - it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); - - spritesheet.resolution = 1; - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); -}); - -describe('PIXI.loaders.parseBitmapFontData', function () -{ - it('should exist', function () - { - expect(parseBitmapFontData).to.be.a('function'); - }); - - // TODO: Test the parser code. -}); diff --git a/packages/loaders/test/index.js b/packages/loaders/test/index.js index f00de5f..72c6554 100644 --- a/packages/loaders/test/index.js +++ b/packages/loaders/test/index.js @@ -1,4 +1,2 @@ -require('./bitmapFontParser'); require('./loader'); -require('./spritesheetParser'); require('./textureParser'); diff --git a/packages/loaders/test/resources/atlas.json b/packages/loaders/test/resources/atlas.json deleted file mode 100644 index 86e65a5..0000000 --- a/packages/loaders/test/resources/atlas.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas.png b/packages/loaders/test/resources/atlas.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/atlas@0.5x.json b/packages/loaders/test/resources/atlas@0.5x.json deleted file mode 100644 index ae990a1..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas@0.5x.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas@0.5x.png b/packages/loaders/test/resources/atlas@0.5x.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font.fnt b/packages/loaders/test/resources/font.fnt deleted file mode 100644 index 56e1060..0000000 --- a/packages/loaders/test/resources/font.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font.png b/packages/loaders/test/resources/font.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font@0.5x.fnt b/packages/loaders/test/resources/font@0.5x.fnt deleted file mode 100644 index 6c247c7..0000000 --- a/packages/loaders/test/resources/font@0.5x.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font@0.5x.png b/packages/loaders/test/resources/font@0.5x.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/spritesheetParser.js b/packages/loaders/test/spritesheetParser.js deleted file mode 100644 index 98890b8..0000000 --- a/packages/loaders/test/spritesheetParser.js +++ /dev/null @@ -1,213 +0,0 @@ -const path = require('path'); -const { spritesheetParser, Resource, Loader, getResourcePath } = require('../'); -const { Texture, BaseTexture } = require('@pixi/core'); - -describe('PIXI.loaders.spritesheetParser', function () -{ - it('should exist and return a function', function () - { - expect(spritesheetParser).to.be.a('function'); - expect(spritesheetParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not JSON', function () - { - const spy = sinon.spy(); - const res = {}; - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is JSON, but improper format', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, {}); - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should load the image & create textures if json is properly formatted', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); - const loader = new Loader(); - const addStub = sinon.stub(loader, 'add'); - const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); - - imgRes.texture = new Texture(new BaseTexture(imgRes.data)); - - addStub.yields(imgRes); - - spritesheetParser().call(loader, res, spy); - - addStub.restore(); - - expect(spy).to.have.been.calledOnce; - expect(addStub).to.have.been.calledWith( - `${res.name}_image`, - `${path.dirname(res.url)}/${res.data.meta.image}` - ); - expect(res).to.have.property('textures') - .that.is.an('object') - .with.keys(Object.keys(getJsonSpritesheet().frames)) - .and.has.property('0.png') - .that.is.an.instanceof(Texture); - }); - - it('should build the image url', function () - { - function getPath(url, image) - { - return getResourcePath({ - url, - data: { meta: { image } }, - }); - } - - let result = getPath('http://some.com/spritesheet.json', 'img.png'); - - expect(result).to.be.equals('http://some.com/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('http://some.com/some/img.png'); - - result = getPath('/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/img.png'); - - result = getPath('/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('/some/img.png'); - }); - - // TODO: Test that rectangles are created correctly. - // TODO: Test that bathc processing works correctly. - // TODO: Test that resolution processing works correctly. - // TODO: Test that metadata is honored. -}); - -function createMockResource(type, data) -{ - const name = `${Math.floor(Date.now() * Math.random())}`; - - return { - url: `http://localhost/doesnt_exist/${name}`, - name, - type, - data, - metadata: {}, - }; -} - -function getJsonSpritesheet() -{ - /* eslint-disable */ - return {"frames": { - "0.png": - { - "frame": {"x":14,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "1.png": - { - "frame": {"x":14,"y":42,"w":12,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, - "sourceSize": {"w":12,"h":14} - }, - "2.png": - { - "frame": {"x":14,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "3.png": - { - "frame": {"x":42,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "4.png": - { - "frame": {"x":28,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "5.png": - { - "frame": {"x":14,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "6.png": - { - "frame": {"x":0,"y":42,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "7.png": - { - "frame": {"x":0,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "8.png": - { - "frame": {"x":0,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "9.png": - { - "frame": {"x":0,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }}, - "meta": { - "app": "http://www.texturepacker.com", - "version": "1.0", - "image": "hud.png", - "format": "RGBA8888", - "size": {"w":64,"h":64}, - "scale": "1", - "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" - } - }; - /* eslint-enable */ -} diff --git a/packages/mixin-app-loader/LICENSE b/packages/mixin-app-loader/LICENSE new file mode 100644 index 0000000..5d16b50 --- /dev/null +++ b/packages/mixin-app-loader/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2013-2017 Mathew Groves, Chad Engler + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/packages/mixin-app-loader/README.md b/packages/mixin-app-loader/README.md new file mode 100644 index 0000000..3e89f86 --- /dev/null +++ b/packages/mixin-app-loader/README.md @@ -0,0 +1,13 @@ +# @pixi/mixin-app-loader + +## Installation + +```bash +npm install @pixi/mixin-app-loader +``` + +## Usage + +```js +import '@pixi/mixin-app-loader'; +``` \ No newline at end of file diff --git a/packages/mixin-app-loader/package.json b/packages/mixin-app-loader/package.json new file mode 100644 index 0000000..869ed4e --- /dev/null +++ b/packages/mixin-app-loader/package.json @@ -0,0 +1,41 @@ +{ + "name": "@pixi/mixin-app-loader", + "version": "5.0.0-alpha", + "main": "lib/mixin-app-loader.js", + "module": "lib/mixin-app-loader.es.js", + "description": "Support for loader in Application", + "author": "Mat Groves", + "contributors": [ + "Matt Karl " + ], + "homepage": "http://pixijs.com/", + "bugs": "https://github.com/pixijs/pixi.js/issues", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/pixijs/pixi.js.git" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "build:dev": "rollup -c", + "build": "rollup -cp", + "watch": "rollup -cw", + "postversion": "npm run build", + "test": "tester" + }, + "files": [ + "lib" + ], + "dependencies": { + "@pixi/app": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" + }, + "devDependencies": { + "@pixi/utils": "^5.0.0-alpha", + "@internal/tester": "^5.0.0-alpha", + "@internal/builder": "^5.0.0-alpha", + "rollup": "^0.50.0" + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/rollup.config.js b/packages/mixin-app-loader/rollup.config.js new file mode 100644 index 0000000..a73c874 --- /dev/null +++ b/packages/mixin-app-loader/rollup.config.js @@ -0,0 +1 @@ +export { default } from '@internal/builder'; diff --git a/packages/mixin-app-loader/src/index.js b/packages/mixin-app-loader/src/index.js new file mode 100644 index 0000000..354997a --- /dev/null +++ b/packages/mixin-app-loader/src/index.js @@ -0,0 +1,38 @@ +import { Application } from '@pixi/app'; +import { Loader, shared } from '@pixi/loaders'; + +Application.prototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperties(Application.prototype, { + loader: { + get() + { + if (!this._loader && this._options) + { + const { sharedLoader } = this._options; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +Application.prototype._parentDestroy = Application.prototype.destroy; +Application.prototype.destroy = function destroy(removeView) +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(removeView); +}; diff --git a/packages/mixin-app-loader/test/.eslintrc.json b/packages/mixin-app-loader/test/.eslintrc.json new file mode 100644 index 0000000..2094b04 --- /dev/null +++ b/packages/mixin-app-loader/test/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "globals": { + "expect": false, + "assert": false, + "sinon": false, + "PIXI": false + }, + "rules": { + "func-names": 0, + "no-unused-expressions": 0 + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/test/index.js b/packages/mixin-app-loader/test/index.js new file mode 100644 index 0000000..3468fe5 --- /dev/null +++ b/packages/mixin-app-loader/test/index.js @@ -0,0 +1,35 @@ +const { Application } = require('@pixi/app'); +const { Loader, shared } = require('@pixi/loaders'); +const { skipHello } = require('@pixi/utils'); + +require('../'); + +skipHello(); + +describe('PIXI.Application#loader', function () +{ + it('should contain loader property', function () + { + const obj = new Application(); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); + + it('should use sharedLoader option', function () + { + const obj = new Application({ sharedLoader: true }); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + expect(obj.loader).to.equal(shared); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); +}); diff --git a/packages/spritesheet/package.json b/packages/spritesheet/package.json index 34749e2..5f35442 100644 --- a/packages/spritesheet/package.json +++ b/packages/spritesheet/package.json @@ -31,7 +31,8 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/math": "^5.0.0-alpha", - "@pixi/utils": "^5.0.0-alpha" + "@pixi/utils": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" }, "devDependencies": { "@internal/builder": "^5.0.0-alpha", diff --git a/packages/spritesheet/src/SpritesheetLoader.js b/packages/spritesheet/src/SpritesheetLoader.js new file mode 100644 index 0000000..c7b34ea --- /dev/null +++ b/packages/spritesheet/src/SpritesheetLoader.js @@ -0,0 +1,83 @@ +import url from 'url'; +import { Resource, Loader } from '@pixi/loaders'; +import Spritesheet from './Spritesheet'; + +/** + * {@link PIXI.loaders.Loader Loader} middleware for loading + * texture atlases that have been created with TexturePacker or + * similar JSON-based spritesheet. This automatically generates + * Texture resources. + * @class + * @memberof PIXI + */ +export default class SpritesheetLoader +{ + /** + * Middleware function to support Spritesheets, this is automatically installed. + * @see PIXI.loaders.Loader.useMiddleware + */ + static middleware() + { + return function spritesheetLoader(resource, next) + { + const imageResourceName = `${resource.name}_image`; + + // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists + if (!resource.data + || resource.type !== Resource.TYPE.JSON + || !resource.data.frames + || this.resources[imageResourceName] + ) + { + next(); + + return; + } + + const loadOptions = { + crossOrigin: resource.crossOrigin, + loadType: Resource.LOAD_TYPE.IMAGE, + metadata: resource.metadata.imageMetadata, + parentResource: resource, + }; + + const resourcePath = SpritesheetLoader.getResourcePath(resource, this.baseUrl); + + // load the image for this sheet + this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) + { + const spritesheet = new Spritesheet( + res.texture.baseTexture, + resource.data, + resource.url + ); + + spritesheet.parse(() => + { + resource.spritesheet = spritesheet; + resource.textures = spritesheet.textures; + next(); + }); + }); + }; + } + + /** + * Get the spritesheets root path + * @param {PIXI.loader.Resource} resource - Resource to check path + * @param {string} baseUrl - Base root url + */ + static getResourcePath(resource, baseUrl) + { + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); + } +} + +// Install loader support for Spritesheet objects +Loader.useMiddleware(SpritesheetLoader.middleware); diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/packages/loaders/src/spritesheetParser.js b/packages/loaders/src/spritesheetParser.js deleted file mode 100644 index f7fa206..0000000 --- a/packages/loaders/src/spritesheetParser.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Resource } from 'resource-loader'; -import url from 'url'; -import { Spritesheet } from '@pixi/spritesheet'; - -export default function () -{ - return function spritesheetParser(resource, next) - { - const imageResourceName = `${resource.name}_image`; - - // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists - if (!resource.data - || resource.type !== Resource.TYPE.JSON - || !resource.data.frames - || this.resources[imageResourceName] - ) - { - next(); - - return; - } - - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - const resourcePath = getResourcePath(resource, this.baseUrl); - - // load the image for this sheet - this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) - { - const spritesheet = new Spritesheet( - res.texture.baseTexture, - resource.data, - resource.url - ); - - spritesheet.parse(() => - { - resource.spritesheet = spritesheet; - resource.textures = spritesheet.textures; - next(); - }); - }); - }; -} - -export function getResourcePath(resource, baseUrl) -{ - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - return resource.data.meta.image; - } - - return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); -} diff --git a/packages/loaders/test/bitmapFontParser.js b/packages/loaders/test/bitmapFontParser.js deleted file mode 100644 index 43ef194..0000000 --- a/packages/loaders/test/bitmapFontParser.js +++ /dev/null @@ -1,325 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const { BitmapText } = require('@pixi/text-bitmap'); -const { BaseTextureCache, TextureCache } = require('@pixi/utils'); -const { Texture, BaseTexture } = require('@pixi/core'); -const { Spritesheet } = require('@pixi/spritesheet'); -const { bitmapFontParser, parseBitmapFontData } = require('../'); - -describe('PIXI.loaders.bitmapFontParser', function () -{ - afterEach(function () - { - for (const font in BitmapText.fonts) - { - delete BitmapText.fonts[font]; - } - for (const baseTexture in BaseTextureCache) - { - delete BaseTextureCache[baseTexture]; - } - for (const texture in TextureCache) - { - delete TextureCache[texture]; - } - }); - - before(function (done) - { - const resolveURL = (url) => path.resolve(this.resources, url); - - this.resources = path.join(__dirname, 'resources'); - this.fontXML = null; - this.fontScaledXML = null; - this.fontImage = null; - this.fontScaledImage = null; - this.atlasImage = null; - this.atlasScaledImage = null; - this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require - this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require - - const loadXML = (url) => new Promise((resolve) => - fs.readFile(resolveURL(url), 'utf8', (err, data) => - { - expect(err).to.be.null; - resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); - })); - - const loadImage = (url) => new Promise((resolve) => - { - const image = new Image(); - - image.onload = () => resolve(image); - image.src = resolveURL(url); - }); - - Promise.all([ - loadXML('font.fnt'), - loadXML('font@0.5x.fnt'), - loadImage('font.png'), - loadImage('font@0.5x.png'), - loadImage('atlas.png'), - loadImage('atlas@0.5x.png'), - ]).then(([ - fontXML, - fontScaledXML, - fontImage, - fontScaledImage, - atlasImage, - atlasScaledImage, - ]) => - { - this.fontXML = fontXML; - this.fontScaledXML = fontScaledXML; - this.fontImage = fontImage; - this.fontScaledImage = fontScaledImage; - this.atlasImage = atlasImage; - this.atlasScaledImage = atlasScaledImage; - done(); - }); - }); - - it('should exist and return a function', function () - { - expect(bitmapFontParser).to.be.a('function'); - expect(bitmapFontParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not XML', function () - { - const spy = sinon.spy(); - const res = {}; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is not properly formatted XML', function () - { - const spy = sinon.spy(); - const res = { data: document.createDocumentFragment() }; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - // TODO: Test the texture cache code path. - // TODO: Test the loading texture code path. - // TODO: Test data-url code paths. - - it('should properly register bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charA.texture.frame.x).to.equal(2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charB.texture.frame.x).to.equal(2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charC.texture.frame.x).to.equal(23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charD.texture.frame.x).to.equal(19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register SCALED bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); - const font = BitmapText.registerFont(this.fontScaledXML, 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register bitmap font NESTED into spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); - - it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); - - spritesheet.resolution = 1; - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); -}); - -describe('PIXI.loaders.parseBitmapFontData', function () -{ - it('should exist', function () - { - expect(parseBitmapFontData).to.be.a('function'); - }); - - // TODO: Test the parser code. -}); diff --git a/packages/loaders/test/index.js b/packages/loaders/test/index.js index f00de5f..72c6554 100644 --- a/packages/loaders/test/index.js +++ b/packages/loaders/test/index.js @@ -1,4 +1,2 @@ -require('./bitmapFontParser'); require('./loader'); -require('./spritesheetParser'); require('./textureParser'); diff --git a/packages/loaders/test/resources/atlas.json b/packages/loaders/test/resources/atlas.json deleted file mode 100644 index 86e65a5..0000000 --- a/packages/loaders/test/resources/atlas.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas.png b/packages/loaders/test/resources/atlas.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/atlas@0.5x.json b/packages/loaders/test/resources/atlas@0.5x.json deleted file mode 100644 index ae990a1..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas@0.5x.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas@0.5x.png b/packages/loaders/test/resources/atlas@0.5x.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font.fnt b/packages/loaders/test/resources/font.fnt deleted file mode 100644 index 56e1060..0000000 --- a/packages/loaders/test/resources/font.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font.png b/packages/loaders/test/resources/font.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font@0.5x.fnt b/packages/loaders/test/resources/font@0.5x.fnt deleted file mode 100644 index 6c247c7..0000000 --- a/packages/loaders/test/resources/font@0.5x.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font@0.5x.png b/packages/loaders/test/resources/font@0.5x.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/spritesheetParser.js b/packages/loaders/test/spritesheetParser.js deleted file mode 100644 index 98890b8..0000000 --- a/packages/loaders/test/spritesheetParser.js +++ /dev/null @@ -1,213 +0,0 @@ -const path = require('path'); -const { spritesheetParser, Resource, Loader, getResourcePath } = require('../'); -const { Texture, BaseTexture } = require('@pixi/core'); - -describe('PIXI.loaders.spritesheetParser', function () -{ - it('should exist and return a function', function () - { - expect(spritesheetParser).to.be.a('function'); - expect(spritesheetParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not JSON', function () - { - const spy = sinon.spy(); - const res = {}; - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is JSON, but improper format', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, {}); - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should load the image & create textures if json is properly formatted', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); - const loader = new Loader(); - const addStub = sinon.stub(loader, 'add'); - const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); - - imgRes.texture = new Texture(new BaseTexture(imgRes.data)); - - addStub.yields(imgRes); - - spritesheetParser().call(loader, res, spy); - - addStub.restore(); - - expect(spy).to.have.been.calledOnce; - expect(addStub).to.have.been.calledWith( - `${res.name}_image`, - `${path.dirname(res.url)}/${res.data.meta.image}` - ); - expect(res).to.have.property('textures') - .that.is.an('object') - .with.keys(Object.keys(getJsonSpritesheet().frames)) - .and.has.property('0.png') - .that.is.an.instanceof(Texture); - }); - - it('should build the image url', function () - { - function getPath(url, image) - { - return getResourcePath({ - url, - data: { meta: { image } }, - }); - } - - let result = getPath('http://some.com/spritesheet.json', 'img.png'); - - expect(result).to.be.equals('http://some.com/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('http://some.com/some/img.png'); - - result = getPath('/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/img.png'); - - result = getPath('/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('/some/img.png'); - }); - - // TODO: Test that rectangles are created correctly. - // TODO: Test that bathc processing works correctly. - // TODO: Test that resolution processing works correctly. - // TODO: Test that metadata is honored. -}); - -function createMockResource(type, data) -{ - const name = `${Math.floor(Date.now() * Math.random())}`; - - return { - url: `http://localhost/doesnt_exist/${name}`, - name, - type, - data, - metadata: {}, - }; -} - -function getJsonSpritesheet() -{ - /* eslint-disable */ - return {"frames": { - "0.png": - { - "frame": {"x":14,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "1.png": - { - "frame": {"x":14,"y":42,"w":12,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, - "sourceSize": {"w":12,"h":14} - }, - "2.png": - { - "frame": {"x":14,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "3.png": - { - "frame": {"x":42,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "4.png": - { - "frame": {"x":28,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "5.png": - { - "frame": {"x":14,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "6.png": - { - "frame": {"x":0,"y":42,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "7.png": - { - "frame": {"x":0,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "8.png": - { - "frame": {"x":0,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "9.png": - { - "frame": {"x":0,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }}, - "meta": { - "app": "http://www.texturepacker.com", - "version": "1.0", - "image": "hud.png", - "format": "RGBA8888", - "size": {"w":64,"h":64}, - "scale": "1", - "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" - } - }; - /* eslint-enable */ -} diff --git a/packages/mixin-app-loader/LICENSE b/packages/mixin-app-loader/LICENSE new file mode 100644 index 0000000..5d16b50 --- /dev/null +++ b/packages/mixin-app-loader/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2013-2017 Mathew Groves, Chad Engler + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/packages/mixin-app-loader/README.md b/packages/mixin-app-loader/README.md new file mode 100644 index 0000000..3e89f86 --- /dev/null +++ b/packages/mixin-app-loader/README.md @@ -0,0 +1,13 @@ +# @pixi/mixin-app-loader + +## Installation + +```bash +npm install @pixi/mixin-app-loader +``` + +## Usage + +```js +import '@pixi/mixin-app-loader'; +``` \ No newline at end of file diff --git a/packages/mixin-app-loader/package.json b/packages/mixin-app-loader/package.json new file mode 100644 index 0000000..869ed4e --- /dev/null +++ b/packages/mixin-app-loader/package.json @@ -0,0 +1,41 @@ +{ + "name": "@pixi/mixin-app-loader", + "version": "5.0.0-alpha", + "main": "lib/mixin-app-loader.js", + "module": "lib/mixin-app-loader.es.js", + "description": "Support for loader in Application", + "author": "Mat Groves", + "contributors": [ + "Matt Karl " + ], + "homepage": "http://pixijs.com/", + "bugs": "https://github.com/pixijs/pixi.js/issues", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/pixijs/pixi.js.git" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "build:dev": "rollup -c", + "build": "rollup -cp", + "watch": "rollup -cw", + "postversion": "npm run build", + "test": "tester" + }, + "files": [ + "lib" + ], + "dependencies": { + "@pixi/app": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" + }, + "devDependencies": { + "@pixi/utils": "^5.0.0-alpha", + "@internal/tester": "^5.0.0-alpha", + "@internal/builder": "^5.0.0-alpha", + "rollup": "^0.50.0" + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/rollup.config.js b/packages/mixin-app-loader/rollup.config.js new file mode 100644 index 0000000..a73c874 --- /dev/null +++ b/packages/mixin-app-loader/rollup.config.js @@ -0,0 +1 @@ +export { default } from '@internal/builder'; diff --git a/packages/mixin-app-loader/src/index.js b/packages/mixin-app-loader/src/index.js new file mode 100644 index 0000000..354997a --- /dev/null +++ b/packages/mixin-app-loader/src/index.js @@ -0,0 +1,38 @@ +import { Application } from '@pixi/app'; +import { Loader, shared } from '@pixi/loaders'; + +Application.prototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperties(Application.prototype, { + loader: { + get() + { + if (!this._loader && this._options) + { + const { sharedLoader } = this._options; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +Application.prototype._parentDestroy = Application.prototype.destroy; +Application.prototype.destroy = function destroy(removeView) +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(removeView); +}; diff --git a/packages/mixin-app-loader/test/.eslintrc.json b/packages/mixin-app-loader/test/.eslintrc.json new file mode 100644 index 0000000..2094b04 --- /dev/null +++ b/packages/mixin-app-loader/test/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "globals": { + "expect": false, + "assert": false, + "sinon": false, + "PIXI": false + }, + "rules": { + "func-names": 0, + "no-unused-expressions": 0 + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/test/index.js b/packages/mixin-app-loader/test/index.js new file mode 100644 index 0000000..3468fe5 --- /dev/null +++ b/packages/mixin-app-loader/test/index.js @@ -0,0 +1,35 @@ +const { Application } = require('@pixi/app'); +const { Loader, shared } = require('@pixi/loaders'); +const { skipHello } = require('@pixi/utils'); + +require('../'); + +skipHello(); + +describe('PIXI.Application#loader', function () +{ + it('should contain loader property', function () + { + const obj = new Application(); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); + + it('should use sharedLoader option', function () + { + const obj = new Application({ sharedLoader: true }); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + expect(obj.loader).to.equal(shared); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); +}); diff --git a/packages/spritesheet/package.json b/packages/spritesheet/package.json index 34749e2..5f35442 100644 --- a/packages/spritesheet/package.json +++ b/packages/spritesheet/package.json @@ -31,7 +31,8 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/math": "^5.0.0-alpha", - "@pixi/utils": "^5.0.0-alpha" + "@pixi/utils": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" }, "devDependencies": { "@internal/builder": "^5.0.0-alpha", diff --git a/packages/spritesheet/src/SpritesheetLoader.js b/packages/spritesheet/src/SpritesheetLoader.js new file mode 100644 index 0000000..c7b34ea --- /dev/null +++ b/packages/spritesheet/src/SpritesheetLoader.js @@ -0,0 +1,83 @@ +import url from 'url'; +import { Resource, Loader } from '@pixi/loaders'; +import Spritesheet from './Spritesheet'; + +/** + * {@link PIXI.loaders.Loader Loader} middleware for loading + * texture atlases that have been created with TexturePacker or + * similar JSON-based spritesheet. This automatically generates + * Texture resources. + * @class + * @memberof PIXI + */ +export default class SpritesheetLoader +{ + /** + * Middleware function to support Spritesheets, this is automatically installed. + * @see PIXI.loaders.Loader.useMiddleware + */ + static middleware() + { + return function spritesheetLoader(resource, next) + { + const imageResourceName = `${resource.name}_image`; + + // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists + if (!resource.data + || resource.type !== Resource.TYPE.JSON + || !resource.data.frames + || this.resources[imageResourceName] + ) + { + next(); + + return; + } + + const loadOptions = { + crossOrigin: resource.crossOrigin, + loadType: Resource.LOAD_TYPE.IMAGE, + metadata: resource.metadata.imageMetadata, + parentResource: resource, + }; + + const resourcePath = SpritesheetLoader.getResourcePath(resource, this.baseUrl); + + // load the image for this sheet + this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) + { + const spritesheet = new Spritesheet( + res.texture.baseTexture, + resource.data, + resource.url + ); + + spritesheet.parse(() => + { + resource.spritesheet = spritesheet; + resource.textures = spritesheet.textures; + next(); + }); + }); + }; + } + + /** + * Get the spritesheets root path + * @param {PIXI.loader.Resource} resource - Resource to check path + * @param {string} baseUrl - Base root url + */ + static getResourcePath(resource, baseUrl) + { + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); + } +} + +// Install loader support for Spritesheet objects +Loader.useMiddleware(SpritesheetLoader.middleware); diff --git a/packages/spritesheet/src/index.js b/packages/spritesheet/src/index.js index 51d96a1..b9ee1e8 100644 --- a/packages/spritesheet/src/index.js +++ b/packages/spritesheet/src/index.js @@ -1 +1,2 @@ export { default as Spritesheet } from './Spritesheet'; +export { default as SpritesheetLoader } from './SpritesheetLoader'; diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/packages/loaders/src/spritesheetParser.js b/packages/loaders/src/spritesheetParser.js deleted file mode 100644 index f7fa206..0000000 --- a/packages/loaders/src/spritesheetParser.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Resource } from 'resource-loader'; -import url from 'url'; -import { Spritesheet } from '@pixi/spritesheet'; - -export default function () -{ - return function spritesheetParser(resource, next) - { - const imageResourceName = `${resource.name}_image`; - - // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists - if (!resource.data - || resource.type !== Resource.TYPE.JSON - || !resource.data.frames - || this.resources[imageResourceName] - ) - { - next(); - - return; - } - - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - const resourcePath = getResourcePath(resource, this.baseUrl); - - // load the image for this sheet - this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) - { - const spritesheet = new Spritesheet( - res.texture.baseTexture, - resource.data, - resource.url - ); - - spritesheet.parse(() => - { - resource.spritesheet = spritesheet; - resource.textures = spritesheet.textures; - next(); - }); - }); - }; -} - -export function getResourcePath(resource, baseUrl) -{ - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - return resource.data.meta.image; - } - - return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); -} diff --git a/packages/loaders/test/bitmapFontParser.js b/packages/loaders/test/bitmapFontParser.js deleted file mode 100644 index 43ef194..0000000 --- a/packages/loaders/test/bitmapFontParser.js +++ /dev/null @@ -1,325 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const { BitmapText } = require('@pixi/text-bitmap'); -const { BaseTextureCache, TextureCache } = require('@pixi/utils'); -const { Texture, BaseTexture } = require('@pixi/core'); -const { Spritesheet } = require('@pixi/spritesheet'); -const { bitmapFontParser, parseBitmapFontData } = require('../'); - -describe('PIXI.loaders.bitmapFontParser', function () -{ - afterEach(function () - { - for (const font in BitmapText.fonts) - { - delete BitmapText.fonts[font]; - } - for (const baseTexture in BaseTextureCache) - { - delete BaseTextureCache[baseTexture]; - } - for (const texture in TextureCache) - { - delete TextureCache[texture]; - } - }); - - before(function (done) - { - const resolveURL = (url) => path.resolve(this.resources, url); - - this.resources = path.join(__dirname, 'resources'); - this.fontXML = null; - this.fontScaledXML = null; - this.fontImage = null; - this.fontScaledImage = null; - this.atlasImage = null; - this.atlasScaledImage = null; - this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require - this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require - - const loadXML = (url) => new Promise((resolve) => - fs.readFile(resolveURL(url), 'utf8', (err, data) => - { - expect(err).to.be.null; - resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); - })); - - const loadImage = (url) => new Promise((resolve) => - { - const image = new Image(); - - image.onload = () => resolve(image); - image.src = resolveURL(url); - }); - - Promise.all([ - loadXML('font.fnt'), - loadXML('font@0.5x.fnt'), - loadImage('font.png'), - loadImage('font@0.5x.png'), - loadImage('atlas.png'), - loadImage('atlas@0.5x.png'), - ]).then(([ - fontXML, - fontScaledXML, - fontImage, - fontScaledImage, - atlasImage, - atlasScaledImage, - ]) => - { - this.fontXML = fontXML; - this.fontScaledXML = fontScaledXML; - this.fontImage = fontImage; - this.fontScaledImage = fontScaledImage; - this.atlasImage = atlasImage; - this.atlasScaledImage = atlasScaledImage; - done(); - }); - }); - - it('should exist and return a function', function () - { - expect(bitmapFontParser).to.be.a('function'); - expect(bitmapFontParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not XML', function () - { - const spy = sinon.spy(); - const res = {}; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is not properly formatted XML', function () - { - const spy = sinon.spy(); - const res = { data: document.createDocumentFragment() }; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - // TODO: Test the texture cache code path. - // TODO: Test the loading texture code path. - // TODO: Test data-url code paths. - - it('should properly register bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charA.texture.frame.x).to.equal(2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charB.texture.frame.x).to.equal(2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charC.texture.frame.x).to.equal(23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charD.texture.frame.x).to.equal(19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register SCALED bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); - const font = BitmapText.registerFont(this.fontScaledXML, 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register bitmap font NESTED into spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); - - it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); - - spritesheet.resolution = 1; - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); -}); - -describe('PIXI.loaders.parseBitmapFontData', function () -{ - it('should exist', function () - { - expect(parseBitmapFontData).to.be.a('function'); - }); - - // TODO: Test the parser code. -}); diff --git a/packages/loaders/test/index.js b/packages/loaders/test/index.js index f00de5f..72c6554 100644 --- a/packages/loaders/test/index.js +++ b/packages/loaders/test/index.js @@ -1,4 +1,2 @@ -require('./bitmapFontParser'); require('./loader'); -require('./spritesheetParser'); require('./textureParser'); diff --git a/packages/loaders/test/resources/atlas.json b/packages/loaders/test/resources/atlas.json deleted file mode 100644 index 86e65a5..0000000 --- a/packages/loaders/test/resources/atlas.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas.png b/packages/loaders/test/resources/atlas.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/atlas@0.5x.json b/packages/loaders/test/resources/atlas@0.5x.json deleted file mode 100644 index ae990a1..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas@0.5x.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas@0.5x.png b/packages/loaders/test/resources/atlas@0.5x.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font.fnt b/packages/loaders/test/resources/font.fnt deleted file mode 100644 index 56e1060..0000000 --- a/packages/loaders/test/resources/font.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font.png b/packages/loaders/test/resources/font.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font@0.5x.fnt b/packages/loaders/test/resources/font@0.5x.fnt deleted file mode 100644 index 6c247c7..0000000 --- a/packages/loaders/test/resources/font@0.5x.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font@0.5x.png b/packages/loaders/test/resources/font@0.5x.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/spritesheetParser.js b/packages/loaders/test/spritesheetParser.js deleted file mode 100644 index 98890b8..0000000 --- a/packages/loaders/test/spritesheetParser.js +++ /dev/null @@ -1,213 +0,0 @@ -const path = require('path'); -const { spritesheetParser, Resource, Loader, getResourcePath } = require('../'); -const { Texture, BaseTexture } = require('@pixi/core'); - -describe('PIXI.loaders.spritesheetParser', function () -{ - it('should exist and return a function', function () - { - expect(spritesheetParser).to.be.a('function'); - expect(spritesheetParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not JSON', function () - { - const spy = sinon.spy(); - const res = {}; - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is JSON, but improper format', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, {}); - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should load the image & create textures if json is properly formatted', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); - const loader = new Loader(); - const addStub = sinon.stub(loader, 'add'); - const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); - - imgRes.texture = new Texture(new BaseTexture(imgRes.data)); - - addStub.yields(imgRes); - - spritesheetParser().call(loader, res, spy); - - addStub.restore(); - - expect(spy).to.have.been.calledOnce; - expect(addStub).to.have.been.calledWith( - `${res.name}_image`, - `${path.dirname(res.url)}/${res.data.meta.image}` - ); - expect(res).to.have.property('textures') - .that.is.an('object') - .with.keys(Object.keys(getJsonSpritesheet().frames)) - .and.has.property('0.png') - .that.is.an.instanceof(Texture); - }); - - it('should build the image url', function () - { - function getPath(url, image) - { - return getResourcePath({ - url, - data: { meta: { image } }, - }); - } - - let result = getPath('http://some.com/spritesheet.json', 'img.png'); - - expect(result).to.be.equals('http://some.com/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('http://some.com/some/img.png'); - - result = getPath('/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/img.png'); - - result = getPath('/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('/some/img.png'); - }); - - // TODO: Test that rectangles are created correctly. - // TODO: Test that bathc processing works correctly. - // TODO: Test that resolution processing works correctly. - // TODO: Test that metadata is honored. -}); - -function createMockResource(type, data) -{ - const name = `${Math.floor(Date.now() * Math.random())}`; - - return { - url: `http://localhost/doesnt_exist/${name}`, - name, - type, - data, - metadata: {}, - }; -} - -function getJsonSpritesheet() -{ - /* eslint-disable */ - return {"frames": { - "0.png": - { - "frame": {"x":14,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "1.png": - { - "frame": {"x":14,"y":42,"w":12,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, - "sourceSize": {"w":12,"h":14} - }, - "2.png": - { - "frame": {"x":14,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "3.png": - { - "frame": {"x":42,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "4.png": - { - "frame": {"x":28,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "5.png": - { - "frame": {"x":14,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "6.png": - { - "frame": {"x":0,"y":42,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "7.png": - { - "frame": {"x":0,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "8.png": - { - "frame": {"x":0,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "9.png": - { - "frame": {"x":0,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }}, - "meta": { - "app": "http://www.texturepacker.com", - "version": "1.0", - "image": "hud.png", - "format": "RGBA8888", - "size": {"w":64,"h":64}, - "scale": "1", - "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" - } - }; - /* eslint-enable */ -} diff --git a/packages/mixin-app-loader/LICENSE b/packages/mixin-app-loader/LICENSE new file mode 100644 index 0000000..5d16b50 --- /dev/null +++ b/packages/mixin-app-loader/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2013-2017 Mathew Groves, Chad Engler + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/packages/mixin-app-loader/README.md b/packages/mixin-app-loader/README.md new file mode 100644 index 0000000..3e89f86 --- /dev/null +++ b/packages/mixin-app-loader/README.md @@ -0,0 +1,13 @@ +# @pixi/mixin-app-loader + +## Installation + +```bash +npm install @pixi/mixin-app-loader +``` + +## Usage + +```js +import '@pixi/mixin-app-loader'; +``` \ No newline at end of file diff --git a/packages/mixin-app-loader/package.json b/packages/mixin-app-loader/package.json new file mode 100644 index 0000000..869ed4e --- /dev/null +++ b/packages/mixin-app-loader/package.json @@ -0,0 +1,41 @@ +{ + "name": "@pixi/mixin-app-loader", + "version": "5.0.0-alpha", + "main": "lib/mixin-app-loader.js", + "module": "lib/mixin-app-loader.es.js", + "description": "Support for loader in Application", + "author": "Mat Groves", + "contributors": [ + "Matt Karl " + ], + "homepage": "http://pixijs.com/", + "bugs": "https://github.com/pixijs/pixi.js/issues", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/pixijs/pixi.js.git" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "build:dev": "rollup -c", + "build": "rollup -cp", + "watch": "rollup -cw", + "postversion": "npm run build", + "test": "tester" + }, + "files": [ + "lib" + ], + "dependencies": { + "@pixi/app": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" + }, + "devDependencies": { + "@pixi/utils": "^5.0.0-alpha", + "@internal/tester": "^5.0.0-alpha", + "@internal/builder": "^5.0.0-alpha", + "rollup": "^0.50.0" + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/rollup.config.js b/packages/mixin-app-loader/rollup.config.js new file mode 100644 index 0000000..a73c874 --- /dev/null +++ b/packages/mixin-app-loader/rollup.config.js @@ -0,0 +1 @@ +export { default } from '@internal/builder'; diff --git a/packages/mixin-app-loader/src/index.js b/packages/mixin-app-loader/src/index.js new file mode 100644 index 0000000..354997a --- /dev/null +++ b/packages/mixin-app-loader/src/index.js @@ -0,0 +1,38 @@ +import { Application } from '@pixi/app'; +import { Loader, shared } from '@pixi/loaders'; + +Application.prototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperties(Application.prototype, { + loader: { + get() + { + if (!this._loader && this._options) + { + const { sharedLoader } = this._options; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +Application.prototype._parentDestroy = Application.prototype.destroy; +Application.prototype.destroy = function destroy(removeView) +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(removeView); +}; diff --git a/packages/mixin-app-loader/test/.eslintrc.json b/packages/mixin-app-loader/test/.eslintrc.json new file mode 100644 index 0000000..2094b04 --- /dev/null +++ b/packages/mixin-app-loader/test/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "globals": { + "expect": false, + "assert": false, + "sinon": false, + "PIXI": false + }, + "rules": { + "func-names": 0, + "no-unused-expressions": 0 + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/test/index.js b/packages/mixin-app-loader/test/index.js new file mode 100644 index 0000000..3468fe5 --- /dev/null +++ b/packages/mixin-app-loader/test/index.js @@ -0,0 +1,35 @@ +const { Application } = require('@pixi/app'); +const { Loader, shared } = require('@pixi/loaders'); +const { skipHello } = require('@pixi/utils'); + +require('../'); + +skipHello(); + +describe('PIXI.Application#loader', function () +{ + it('should contain loader property', function () + { + const obj = new Application(); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); + + it('should use sharedLoader option', function () + { + const obj = new Application({ sharedLoader: true }); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + expect(obj.loader).to.equal(shared); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); +}); diff --git a/packages/spritesheet/package.json b/packages/spritesheet/package.json index 34749e2..5f35442 100644 --- a/packages/spritesheet/package.json +++ b/packages/spritesheet/package.json @@ -31,7 +31,8 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/math": "^5.0.0-alpha", - "@pixi/utils": "^5.0.0-alpha" + "@pixi/utils": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" }, "devDependencies": { "@internal/builder": "^5.0.0-alpha", diff --git a/packages/spritesheet/src/SpritesheetLoader.js b/packages/spritesheet/src/SpritesheetLoader.js new file mode 100644 index 0000000..c7b34ea --- /dev/null +++ b/packages/spritesheet/src/SpritesheetLoader.js @@ -0,0 +1,83 @@ +import url from 'url'; +import { Resource, Loader } from '@pixi/loaders'; +import Spritesheet from './Spritesheet'; + +/** + * {@link PIXI.loaders.Loader Loader} middleware for loading + * texture atlases that have been created with TexturePacker or + * similar JSON-based spritesheet. This automatically generates + * Texture resources. + * @class + * @memberof PIXI + */ +export default class SpritesheetLoader +{ + /** + * Middleware function to support Spritesheets, this is automatically installed. + * @see PIXI.loaders.Loader.useMiddleware + */ + static middleware() + { + return function spritesheetLoader(resource, next) + { + const imageResourceName = `${resource.name}_image`; + + // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists + if (!resource.data + || resource.type !== Resource.TYPE.JSON + || !resource.data.frames + || this.resources[imageResourceName] + ) + { + next(); + + return; + } + + const loadOptions = { + crossOrigin: resource.crossOrigin, + loadType: Resource.LOAD_TYPE.IMAGE, + metadata: resource.metadata.imageMetadata, + parentResource: resource, + }; + + const resourcePath = SpritesheetLoader.getResourcePath(resource, this.baseUrl); + + // load the image for this sheet + this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) + { + const spritesheet = new Spritesheet( + res.texture.baseTexture, + resource.data, + resource.url + ); + + spritesheet.parse(() => + { + resource.spritesheet = spritesheet; + resource.textures = spritesheet.textures; + next(); + }); + }); + }; + } + + /** + * Get the spritesheets root path + * @param {PIXI.loader.Resource} resource - Resource to check path + * @param {string} baseUrl - Base root url + */ + static getResourcePath(resource, baseUrl) + { + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); + } +} + +// Install loader support for Spritesheet objects +Loader.useMiddleware(SpritesheetLoader.middleware); diff --git a/packages/spritesheet/src/index.js b/packages/spritesheet/src/index.js index 51d96a1..b9ee1e8 100644 --- a/packages/spritesheet/src/index.js +++ b/packages/spritesheet/src/index.js @@ -1 +1,2 @@ export { default as Spritesheet } from './Spritesheet'; +export { default as SpritesheetLoader } from './SpritesheetLoader'; diff --git a/packages/spritesheet/test/Spritesheet.js b/packages/spritesheet/test/Spritesheet.js new file mode 100644 index 0000000..35d8688 --- /dev/null +++ b/packages/spritesheet/test/Spritesheet.js @@ -0,0 +1,105 @@ +const { Spritesheet } = require('../'); +const { BaseTexture, Texture } = require('@pixi/core'); +const path = require('path'); + +describe('PIXI.Spritesheet', function () +{ + before(function () + { + this.resources = path.join(__dirname, 'resources'); + this.validate = function (spritesheet, done) + { + spritesheet.parse(function (textures) + { + const id = 'goldmine_10_5.png'; + const width = Math.floor(spritesheet.data.frames[id].frame.w); + const height = Math.floor(spritesheet.data.frames[id].frame.h); + + expect(Object.keys(textures).length).to.equal(1); + expect(Object.keys(spritesheet.textures).length).to.equal(1); + expect(textures[id]).to.be.an.instanceof(Texture); + expect(textures[id].width).to.equal(width / spritesheet.resolution); + expect(textures[id].height).to.equal(height / spritesheet.resolution); + expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); + spritesheet.destroy(true); + expect(spritesheet.textures).to.be.null; + expect(spritesheet.baseTexture).to.be.null; + done(); + }); + }; + }); + + it('should exist on PIXI', function () + { + expect(Spritesheet).to.be.a.function; + expect(Spritesheet.BATCH_SIZE).to.be.a.number; + }); + + it('should create an instance', function () + { + const baseTexture = new BaseTexture(); + const data = { + frames: {}, + meta: {}, + }; + + const spritesheet = new Spritesheet(baseTexture, data); + + expect(spritesheet.data).to.equal(data); + expect(spritesheet.baseTexture).to.equal(baseTexture); + expect(spritesheet.resolution).to.equal(1); + + spritesheet.destroy(true); + }); + + it('should create instance with scale resolution', function (done) + { + const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require + const image = new Image(); + + image.src = path.join(this.resources, data.meta.image); + image.onload = () => + { + const baseTexture = new BaseTexture(image, null, 1); + const spritesheet = new Spritesheet(baseTexture, data); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1.png'); + expect(spritesheet.resolution).to.equal(0.5); + this.validate(spritesheet, done); + }; + }); + + it('should create instance with BaseTexture source scale', function (done) + { + const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require + const baseTexture = new BaseTexture.fromImage(data.meta.image, undefined, undefined, 1.5); + const spritesheet = new Spritesheet(baseTexture, data); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1.png'); + expect(spritesheet.resolution).to.equal(0.5); + + this.validate(spritesheet, done); + }); + + it('should create instance with filename resolution', function (done) + { + const uri = path.resolve(this.resources, 'building1@2x.json'); + const data = require(uri); // eslint-disable-line global-require + const image = new Image(); + + image.src = path.join(this.resources, data.meta.image); + image.onload = () => + { + const baseTexture = new BaseTexture(image, null, 1); + const spritesheet = new Spritesheet(baseTexture, data, uri); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1@2x.png'); + expect(spritesheet.resolution).to.equal(2); + + this.validate(spritesheet, done); + }; + }); +}); diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/packages/loaders/src/spritesheetParser.js b/packages/loaders/src/spritesheetParser.js deleted file mode 100644 index f7fa206..0000000 --- a/packages/loaders/src/spritesheetParser.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Resource } from 'resource-loader'; -import url from 'url'; -import { Spritesheet } from '@pixi/spritesheet'; - -export default function () -{ - return function spritesheetParser(resource, next) - { - const imageResourceName = `${resource.name}_image`; - - // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists - if (!resource.data - || resource.type !== Resource.TYPE.JSON - || !resource.data.frames - || this.resources[imageResourceName] - ) - { - next(); - - return; - } - - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - const resourcePath = getResourcePath(resource, this.baseUrl); - - // load the image for this sheet - this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) - { - const spritesheet = new Spritesheet( - res.texture.baseTexture, - resource.data, - resource.url - ); - - spritesheet.parse(() => - { - resource.spritesheet = spritesheet; - resource.textures = spritesheet.textures; - next(); - }); - }); - }; -} - -export function getResourcePath(resource, baseUrl) -{ - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - return resource.data.meta.image; - } - - return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); -} diff --git a/packages/loaders/test/bitmapFontParser.js b/packages/loaders/test/bitmapFontParser.js deleted file mode 100644 index 43ef194..0000000 --- a/packages/loaders/test/bitmapFontParser.js +++ /dev/null @@ -1,325 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const { BitmapText } = require('@pixi/text-bitmap'); -const { BaseTextureCache, TextureCache } = require('@pixi/utils'); -const { Texture, BaseTexture } = require('@pixi/core'); -const { Spritesheet } = require('@pixi/spritesheet'); -const { bitmapFontParser, parseBitmapFontData } = require('../'); - -describe('PIXI.loaders.bitmapFontParser', function () -{ - afterEach(function () - { - for (const font in BitmapText.fonts) - { - delete BitmapText.fonts[font]; - } - for (const baseTexture in BaseTextureCache) - { - delete BaseTextureCache[baseTexture]; - } - for (const texture in TextureCache) - { - delete TextureCache[texture]; - } - }); - - before(function (done) - { - const resolveURL = (url) => path.resolve(this.resources, url); - - this.resources = path.join(__dirname, 'resources'); - this.fontXML = null; - this.fontScaledXML = null; - this.fontImage = null; - this.fontScaledImage = null; - this.atlasImage = null; - this.atlasScaledImage = null; - this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require - this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require - - const loadXML = (url) => new Promise((resolve) => - fs.readFile(resolveURL(url), 'utf8', (err, data) => - { - expect(err).to.be.null; - resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); - })); - - const loadImage = (url) => new Promise((resolve) => - { - const image = new Image(); - - image.onload = () => resolve(image); - image.src = resolveURL(url); - }); - - Promise.all([ - loadXML('font.fnt'), - loadXML('font@0.5x.fnt'), - loadImage('font.png'), - loadImage('font@0.5x.png'), - loadImage('atlas.png'), - loadImage('atlas@0.5x.png'), - ]).then(([ - fontXML, - fontScaledXML, - fontImage, - fontScaledImage, - atlasImage, - atlasScaledImage, - ]) => - { - this.fontXML = fontXML; - this.fontScaledXML = fontScaledXML; - this.fontImage = fontImage; - this.fontScaledImage = fontScaledImage; - this.atlasImage = atlasImage; - this.atlasScaledImage = atlasScaledImage; - done(); - }); - }); - - it('should exist and return a function', function () - { - expect(bitmapFontParser).to.be.a('function'); - expect(bitmapFontParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not XML', function () - { - const spy = sinon.spy(); - const res = {}; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is not properly formatted XML', function () - { - const spy = sinon.spy(); - const res = { data: document.createDocumentFragment() }; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - // TODO: Test the texture cache code path. - // TODO: Test the loading texture code path. - // TODO: Test data-url code paths. - - it('should properly register bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charA.texture.frame.x).to.equal(2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charB.texture.frame.x).to.equal(2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charC.texture.frame.x).to.equal(23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charD.texture.frame.x).to.equal(19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register SCALED bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); - const font = BitmapText.registerFont(this.fontScaledXML, 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register bitmap font NESTED into spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); - - it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); - - spritesheet.resolution = 1; - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); -}); - -describe('PIXI.loaders.parseBitmapFontData', function () -{ - it('should exist', function () - { - expect(parseBitmapFontData).to.be.a('function'); - }); - - // TODO: Test the parser code. -}); diff --git a/packages/loaders/test/index.js b/packages/loaders/test/index.js index f00de5f..72c6554 100644 --- a/packages/loaders/test/index.js +++ b/packages/loaders/test/index.js @@ -1,4 +1,2 @@ -require('./bitmapFontParser'); require('./loader'); -require('./spritesheetParser'); require('./textureParser'); diff --git a/packages/loaders/test/resources/atlas.json b/packages/loaders/test/resources/atlas.json deleted file mode 100644 index 86e65a5..0000000 --- a/packages/loaders/test/resources/atlas.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas.png b/packages/loaders/test/resources/atlas.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/atlas@0.5x.json b/packages/loaders/test/resources/atlas@0.5x.json deleted file mode 100644 index ae990a1..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas@0.5x.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas@0.5x.png b/packages/loaders/test/resources/atlas@0.5x.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font.fnt b/packages/loaders/test/resources/font.fnt deleted file mode 100644 index 56e1060..0000000 --- a/packages/loaders/test/resources/font.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font.png b/packages/loaders/test/resources/font.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font@0.5x.fnt b/packages/loaders/test/resources/font@0.5x.fnt deleted file mode 100644 index 6c247c7..0000000 --- a/packages/loaders/test/resources/font@0.5x.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font@0.5x.png b/packages/loaders/test/resources/font@0.5x.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/spritesheetParser.js b/packages/loaders/test/spritesheetParser.js deleted file mode 100644 index 98890b8..0000000 --- a/packages/loaders/test/spritesheetParser.js +++ /dev/null @@ -1,213 +0,0 @@ -const path = require('path'); -const { spritesheetParser, Resource, Loader, getResourcePath } = require('../'); -const { Texture, BaseTexture } = require('@pixi/core'); - -describe('PIXI.loaders.spritesheetParser', function () -{ - it('should exist and return a function', function () - { - expect(spritesheetParser).to.be.a('function'); - expect(spritesheetParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not JSON', function () - { - const spy = sinon.spy(); - const res = {}; - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is JSON, but improper format', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, {}); - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should load the image & create textures if json is properly formatted', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); - const loader = new Loader(); - const addStub = sinon.stub(loader, 'add'); - const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); - - imgRes.texture = new Texture(new BaseTexture(imgRes.data)); - - addStub.yields(imgRes); - - spritesheetParser().call(loader, res, spy); - - addStub.restore(); - - expect(spy).to.have.been.calledOnce; - expect(addStub).to.have.been.calledWith( - `${res.name}_image`, - `${path.dirname(res.url)}/${res.data.meta.image}` - ); - expect(res).to.have.property('textures') - .that.is.an('object') - .with.keys(Object.keys(getJsonSpritesheet().frames)) - .and.has.property('0.png') - .that.is.an.instanceof(Texture); - }); - - it('should build the image url', function () - { - function getPath(url, image) - { - return getResourcePath({ - url, - data: { meta: { image } }, - }); - } - - let result = getPath('http://some.com/spritesheet.json', 'img.png'); - - expect(result).to.be.equals('http://some.com/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('http://some.com/some/img.png'); - - result = getPath('/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/img.png'); - - result = getPath('/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('/some/img.png'); - }); - - // TODO: Test that rectangles are created correctly. - // TODO: Test that bathc processing works correctly. - // TODO: Test that resolution processing works correctly. - // TODO: Test that metadata is honored. -}); - -function createMockResource(type, data) -{ - const name = `${Math.floor(Date.now() * Math.random())}`; - - return { - url: `http://localhost/doesnt_exist/${name}`, - name, - type, - data, - metadata: {}, - }; -} - -function getJsonSpritesheet() -{ - /* eslint-disable */ - return {"frames": { - "0.png": - { - "frame": {"x":14,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "1.png": - { - "frame": {"x":14,"y":42,"w":12,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, - "sourceSize": {"w":12,"h":14} - }, - "2.png": - { - "frame": {"x":14,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "3.png": - { - "frame": {"x":42,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "4.png": - { - "frame": {"x":28,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "5.png": - { - "frame": {"x":14,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "6.png": - { - "frame": {"x":0,"y":42,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "7.png": - { - "frame": {"x":0,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "8.png": - { - "frame": {"x":0,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "9.png": - { - "frame": {"x":0,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }}, - "meta": { - "app": "http://www.texturepacker.com", - "version": "1.0", - "image": "hud.png", - "format": "RGBA8888", - "size": {"w":64,"h":64}, - "scale": "1", - "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" - } - }; - /* eslint-enable */ -} diff --git a/packages/mixin-app-loader/LICENSE b/packages/mixin-app-loader/LICENSE new file mode 100644 index 0000000..5d16b50 --- /dev/null +++ b/packages/mixin-app-loader/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2013-2017 Mathew Groves, Chad Engler + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/packages/mixin-app-loader/README.md b/packages/mixin-app-loader/README.md new file mode 100644 index 0000000..3e89f86 --- /dev/null +++ b/packages/mixin-app-loader/README.md @@ -0,0 +1,13 @@ +# @pixi/mixin-app-loader + +## Installation + +```bash +npm install @pixi/mixin-app-loader +``` + +## Usage + +```js +import '@pixi/mixin-app-loader'; +``` \ No newline at end of file diff --git a/packages/mixin-app-loader/package.json b/packages/mixin-app-loader/package.json new file mode 100644 index 0000000..869ed4e --- /dev/null +++ b/packages/mixin-app-loader/package.json @@ -0,0 +1,41 @@ +{ + "name": "@pixi/mixin-app-loader", + "version": "5.0.0-alpha", + "main": "lib/mixin-app-loader.js", + "module": "lib/mixin-app-loader.es.js", + "description": "Support for loader in Application", + "author": "Mat Groves", + "contributors": [ + "Matt Karl " + ], + "homepage": "http://pixijs.com/", + "bugs": "https://github.com/pixijs/pixi.js/issues", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/pixijs/pixi.js.git" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "build:dev": "rollup -c", + "build": "rollup -cp", + "watch": "rollup -cw", + "postversion": "npm run build", + "test": "tester" + }, + "files": [ + "lib" + ], + "dependencies": { + "@pixi/app": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" + }, + "devDependencies": { + "@pixi/utils": "^5.0.0-alpha", + "@internal/tester": "^5.0.0-alpha", + "@internal/builder": "^5.0.0-alpha", + "rollup": "^0.50.0" + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/rollup.config.js b/packages/mixin-app-loader/rollup.config.js new file mode 100644 index 0000000..a73c874 --- /dev/null +++ b/packages/mixin-app-loader/rollup.config.js @@ -0,0 +1 @@ +export { default } from '@internal/builder'; diff --git a/packages/mixin-app-loader/src/index.js b/packages/mixin-app-loader/src/index.js new file mode 100644 index 0000000..354997a --- /dev/null +++ b/packages/mixin-app-loader/src/index.js @@ -0,0 +1,38 @@ +import { Application } from '@pixi/app'; +import { Loader, shared } from '@pixi/loaders'; + +Application.prototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperties(Application.prototype, { + loader: { + get() + { + if (!this._loader && this._options) + { + const { sharedLoader } = this._options; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +Application.prototype._parentDestroy = Application.prototype.destroy; +Application.prototype.destroy = function destroy(removeView) +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(removeView); +}; diff --git a/packages/mixin-app-loader/test/.eslintrc.json b/packages/mixin-app-loader/test/.eslintrc.json new file mode 100644 index 0000000..2094b04 --- /dev/null +++ b/packages/mixin-app-loader/test/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "globals": { + "expect": false, + "assert": false, + "sinon": false, + "PIXI": false + }, + "rules": { + "func-names": 0, + "no-unused-expressions": 0 + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/test/index.js b/packages/mixin-app-loader/test/index.js new file mode 100644 index 0000000..3468fe5 --- /dev/null +++ b/packages/mixin-app-loader/test/index.js @@ -0,0 +1,35 @@ +const { Application } = require('@pixi/app'); +const { Loader, shared } = require('@pixi/loaders'); +const { skipHello } = require('@pixi/utils'); + +require('../'); + +skipHello(); + +describe('PIXI.Application#loader', function () +{ + it('should contain loader property', function () + { + const obj = new Application(); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); + + it('should use sharedLoader option', function () + { + const obj = new Application({ sharedLoader: true }); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + expect(obj.loader).to.equal(shared); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); +}); diff --git a/packages/spritesheet/package.json b/packages/spritesheet/package.json index 34749e2..5f35442 100644 --- a/packages/spritesheet/package.json +++ b/packages/spritesheet/package.json @@ -31,7 +31,8 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/math": "^5.0.0-alpha", - "@pixi/utils": "^5.0.0-alpha" + "@pixi/utils": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" }, "devDependencies": { "@internal/builder": "^5.0.0-alpha", diff --git a/packages/spritesheet/src/SpritesheetLoader.js b/packages/spritesheet/src/SpritesheetLoader.js new file mode 100644 index 0000000..c7b34ea --- /dev/null +++ b/packages/spritesheet/src/SpritesheetLoader.js @@ -0,0 +1,83 @@ +import url from 'url'; +import { Resource, Loader } from '@pixi/loaders'; +import Spritesheet from './Spritesheet'; + +/** + * {@link PIXI.loaders.Loader Loader} middleware for loading + * texture atlases that have been created with TexturePacker or + * similar JSON-based spritesheet. This automatically generates + * Texture resources. + * @class + * @memberof PIXI + */ +export default class SpritesheetLoader +{ + /** + * Middleware function to support Spritesheets, this is automatically installed. + * @see PIXI.loaders.Loader.useMiddleware + */ + static middleware() + { + return function spritesheetLoader(resource, next) + { + const imageResourceName = `${resource.name}_image`; + + // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists + if (!resource.data + || resource.type !== Resource.TYPE.JSON + || !resource.data.frames + || this.resources[imageResourceName] + ) + { + next(); + + return; + } + + const loadOptions = { + crossOrigin: resource.crossOrigin, + loadType: Resource.LOAD_TYPE.IMAGE, + metadata: resource.metadata.imageMetadata, + parentResource: resource, + }; + + const resourcePath = SpritesheetLoader.getResourcePath(resource, this.baseUrl); + + // load the image for this sheet + this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) + { + const spritesheet = new Spritesheet( + res.texture.baseTexture, + resource.data, + resource.url + ); + + spritesheet.parse(() => + { + resource.spritesheet = spritesheet; + resource.textures = spritesheet.textures; + next(); + }); + }); + }; + } + + /** + * Get the spritesheets root path + * @param {PIXI.loader.Resource} resource - Resource to check path + * @param {string} baseUrl - Base root url + */ + static getResourcePath(resource, baseUrl) + { + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); + } +} + +// Install loader support for Spritesheet objects +Loader.useMiddleware(SpritesheetLoader.middleware); diff --git a/packages/spritesheet/src/index.js b/packages/spritesheet/src/index.js index 51d96a1..b9ee1e8 100644 --- a/packages/spritesheet/src/index.js +++ b/packages/spritesheet/src/index.js @@ -1 +1,2 @@ export { default as Spritesheet } from './Spritesheet'; +export { default as SpritesheetLoader } from './SpritesheetLoader'; diff --git a/packages/spritesheet/test/Spritesheet.js b/packages/spritesheet/test/Spritesheet.js new file mode 100644 index 0000000..35d8688 --- /dev/null +++ b/packages/spritesheet/test/Spritesheet.js @@ -0,0 +1,105 @@ +const { Spritesheet } = require('../'); +const { BaseTexture, Texture } = require('@pixi/core'); +const path = require('path'); + +describe('PIXI.Spritesheet', function () +{ + before(function () + { + this.resources = path.join(__dirname, 'resources'); + this.validate = function (spritesheet, done) + { + spritesheet.parse(function (textures) + { + const id = 'goldmine_10_5.png'; + const width = Math.floor(spritesheet.data.frames[id].frame.w); + const height = Math.floor(spritesheet.data.frames[id].frame.h); + + expect(Object.keys(textures).length).to.equal(1); + expect(Object.keys(spritesheet.textures).length).to.equal(1); + expect(textures[id]).to.be.an.instanceof(Texture); + expect(textures[id].width).to.equal(width / spritesheet.resolution); + expect(textures[id].height).to.equal(height / spritesheet.resolution); + expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); + spritesheet.destroy(true); + expect(spritesheet.textures).to.be.null; + expect(spritesheet.baseTexture).to.be.null; + done(); + }); + }; + }); + + it('should exist on PIXI', function () + { + expect(Spritesheet).to.be.a.function; + expect(Spritesheet.BATCH_SIZE).to.be.a.number; + }); + + it('should create an instance', function () + { + const baseTexture = new BaseTexture(); + const data = { + frames: {}, + meta: {}, + }; + + const spritesheet = new Spritesheet(baseTexture, data); + + expect(spritesheet.data).to.equal(data); + expect(spritesheet.baseTexture).to.equal(baseTexture); + expect(spritesheet.resolution).to.equal(1); + + spritesheet.destroy(true); + }); + + it('should create instance with scale resolution', function (done) + { + const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require + const image = new Image(); + + image.src = path.join(this.resources, data.meta.image); + image.onload = () => + { + const baseTexture = new BaseTexture(image, null, 1); + const spritesheet = new Spritesheet(baseTexture, data); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1.png'); + expect(spritesheet.resolution).to.equal(0.5); + this.validate(spritesheet, done); + }; + }); + + it('should create instance with BaseTexture source scale', function (done) + { + const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require + const baseTexture = new BaseTexture.fromImage(data.meta.image, undefined, undefined, 1.5); + const spritesheet = new Spritesheet(baseTexture, data); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1.png'); + expect(spritesheet.resolution).to.equal(0.5); + + this.validate(spritesheet, done); + }); + + it('should create instance with filename resolution', function (done) + { + const uri = path.resolve(this.resources, 'building1@2x.json'); + const data = require(uri); // eslint-disable-line global-require + const image = new Image(); + + image.src = path.join(this.resources, data.meta.image); + image.onload = () => + { + const baseTexture = new BaseTexture(image, null, 1); + const spritesheet = new Spritesheet(baseTexture, data, uri); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1@2x.png'); + expect(spritesheet.resolution).to.equal(2); + + this.validate(spritesheet, done); + }; + }); +}); diff --git a/packages/spritesheet/test/SpritesheetLoader.js b/packages/spritesheet/test/SpritesheetLoader.js new file mode 100644 index 0000000..e4205ec --- /dev/null +++ b/packages/spritesheet/test/SpritesheetLoader.js @@ -0,0 +1,214 @@ +const path = require('path'); +const { Loader, Resource } = require('@pixi/loaders'); +const { Texture, BaseTexture } = require('@pixi/core'); +const { SpritesheetLoader } = require('../'); + +describe('PIXI.SpritesheetLoader', function () +{ + it('should exist and return a function', function () + { + expect(SpritesheetLoader.middleware).to.be.a('function'); + expect(SpritesheetLoader.middleware()).to.be.a('function'); + }); + + it('should do nothing if the resource is not JSON', function () + { + const spy = sinon.spy(); + const res = {}; + + SpritesheetLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should do nothing if the resource is JSON, but improper format', function () + { + const spy = sinon.spy(); + const res = createMockResource(Resource.TYPE.JSON, {}); + + SpritesheetLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should load the image & create textures if json is properly formatted', function () + { + const spy = sinon.spy(); + const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); + const loader = new Loader(); + const addStub = sinon.stub(loader, 'add'); + const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); + + imgRes.texture = new Texture(new BaseTexture(imgRes.data)); + + addStub.yields(imgRes); + + SpritesheetLoader.middleware().call(loader, res, spy); + + addStub.restore(); + + expect(spy).to.have.been.calledOnce; + expect(addStub).to.have.been.calledWith( + `${res.name}_image`, + `${path.dirname(res.url)}/${res.data.meta.image}` + ); + expect(res).to.have.property('textures') + .that.is.an('object') + .with.keys(Object.keys(getJsonSpritesheet().frames)) + .and.has.property('0.png') + .that.is.an.instanceof(Texture); + }); + + it('should build the image url', function () + { + function getPath(url, image) + { + return SpritesheetLoader.getResourcePath({ + url, + data: { meta: { image } }, + }); + } + + let result = getPath('http://some.com/spritesheet.json', 'img.png'); + + expect(result).to.be.equals('http://some.com/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); + expect(result).to.be.equals('http://some.com/some/dir/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); + expect(result).to.be.equals('http://some.com/some/dir/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); + expect(result).to.be.equals('http://some.com/some/img.png'); + + result = getPath('/spritesheet.json', 'img.png'); + expect(result).to.be.equals('/img.png'); + + result = getPath('/some/dir/spritesheet.json', 'img.png'); + expect(result).to.be.equals('/some/dir/img.png'); + + result = getPath('/some/dir/spritesheet.json', './img.png'); + expect(result).to.be.equals('/some/dir/img.png'); + + result = getPath('/some/dir/spritesheet.json', '../img.png'); + expect(result).to.be.equals('/some/img.png'); + }); + + // TODO: Test that rectangles are created correctly. + // TODO: Test that bathc processing works correctly. + // TODO: Test that resolution processing works correctly. + // TODO: Test that metadata is honored. +}); + +function createMockResource(type, data) +{ + const name = `${Math.floor(Date.now() * Math.random())}`; + + return { + url: `http://localhost/doesnt_exist/${name}`, + name, + type, + data, + metadata: {}, + }; +} + +function getJsonSpritesheet() +{ + /* eslint-disable */ + return {"frames": { + "0.png": + { + "frame": {"x":14,"y":28,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "1.png": + { + "frame": {"x":14,"y":42,"w":12,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, + "sourceSize": {"w":12,"h":14} + }, + "2.png": + { + "frame": {"x":14,"y":14,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "3.png": + { + "frame": {"x":42,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "4.png": + { + "frame": {"x":28,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "5.png": + { + "frame": {"x":14,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "6.png": + { + "frame": {"x":0,"y":42,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "7.png": + { + "frame": {"x":0,"y":28,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "8.png": + { + "frame": {"x":0,"y":14,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "9.png": + { + "frame": {"x":0,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }}, + "meta": { + "app": "http://www.texturepacker.com", + "version": "1.0", + "image": "hud.png", + "format": "RGBA8888", + "size": {"w":64,"h":64}, + "scale": "1", + "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" + } + }; + /* eslint-enable */ +} diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/packages/loaders/src/spritesheetParser.js b/packages/loaders/src/spritesheetParser.js deleted file mode 100644 index f7fa206..0000000 --- a/packages/loaders/src/spritesheetParser.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Resource } from 'resource-loader'; -import url from 'url'; -import { Spritesheet } from '@pixi/spritesheet'; - -export default function () -{ - return function spritesheetParser(resource, next) - { - const imageResourceName = `${resource.name}_image`; - - // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists - if (!resource.data - || resource.type !== Resource.TYPE.JSON - || !resource.data.frames - || this.resources[imageResourceName] - ) - { - next(); - - return; - } - - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - const resourcePath = getResourcePath(resource, this.baseUrl); - - // load the image for this sheet - this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) - { - const spritesheet = new Spritesheet( - res.texture.baseTexture, - resource.data, - resource.url - ); - - spritesheet.parse(() => - { - resource.spritesheet = spritesheet; - resource.textures = spritesheet.textures; - next(); - }); - }); - }; -} - -export function getResourcePath(resource, baseUrl) -{ - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - return resource.data.meta.image; - } - - return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); -} diff --git a/packages/loaders/test/bitmapFontParser.js b/packages/loaders/test/bitmapFontParser.js deleted file mode 100644 index 43ef194..0000000 --- a/packages/loaders/test/bitmapFontParser.js +++ /dev/null @@ -1,325 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const { BitmapText } = require('@pixi/text-bitmap'); -const { BaseTextureCache, TextureCache } = require('@pixi/utils'); -const { Texture, BaseTexture } = require('@pixi/core'); -const { Spritesheet } = require('@pixi/spritesheet'); -const { bitmapFontParser, parseBitmapFontData } = require('../'); - -describe('PIXI.loaders.bitmapFontParser', function () -{ - afterEach(function () - { - for (const font in BitmapText.fonts) - { - delete BitmapText.fonts[font]; - } - for (const baseTexture in BaseTextureCache) - { - delete BaseTextureCache[baseTexture]; - } - for (const texture in TextureCache) - { - delete TextureCache[texture]; - } - }); - - before(function (done) - { - const resolveURL = (url) => path.resolve(this.resources, url); - - this.resources = path.join(__dirname, 'resources'); - this.fontXML = null; - this.fontScaledXML = null; - this.fontImage = null; - this.fontScaledImage = null; - this.atlasImage = null; - this.atlasScaledImage = null; - this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require - this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require - - const loadXML = (url) => new Promise((resolve) => - fs.readFile(resolveURL(url), 'utf8', (err, data) => - { - expect(err).to.be.null; - resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); - })); - - const loadImage = (url) => new Promise((resolve) => - { - const image = new Image(); - - image.onload = () => resolve(image); - image.src = resolveURL(url); - }); - - Promise.all([ - loadXML('font.fnt'), - loadXML('font@0.5x.fnt'), - loadImage('font.png'), - loadImage('font@0.5x.png'), - loadImage('atlas.png'), - loadImage('atlas@0.5x.png'), - ]).then(([ - fontXML, - fontScaledXML, - fontImage, - fontScaledImage, - atlasImage, - atlasScaledImage, - ]) => - { - this.fontXML = fontXML; - this.fontScaledXML = fontScaledXML; - this.fontImage = fontImage; - this.fontScaledImage = fontScaledImage; - this.atlasImage = atlasImage; - this.atlasScaledImage = atlasScaledImage; - done(); - }); - }); - - it('should exist and return a function', function () - { - expect(bitmapFontParser).to.be.a('function'); - expect(bitmapFontParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not XML', function () - { - const spy = sinon.spy(); - const res = {}; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is not properly formatted XML', function () - { - const spy = sinon.spy(); - const res = { data: document.createDocumentFragment() }; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - // TODO: Test the texture cache code path. - // TODO: Test the loading texture code path. - // TODO: Test data-url code paths. - - it('should properly register bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charA.texture.frame.x).to.equal(2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charB.texture.frame.x).to.equal(2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charC.texture.frame.x).to.equal(23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charD.texture.frame.x).to.equal(19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register SCALED bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); - const font = BitmapText.registerFont(this.fontScaledXML, 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register bitmap font NESTED into spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); - - it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); - - spritesheet.resolution = 1; - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); -}); - -describe('PIXI.loaders.parseBitmapFontData', function () -{ - it('should exist', function () - { - expect(parseBitmapFontData).to.be.a('function'); - }); - - // TODO: Test the parser code. -}); diff --git a/packages/loaders/test/index.js b/packages/loaders/test/index.js index f00de5f..72c6554 100644 --- a/packages/loaders/test/index.js +++ b/packages/loaders/test/index.js @@ -1,4 +1,2 @@ -require('./bitmapFontParser'); require('./loader'); -require('./spritesheetParser'); require('./textureParser'); diff --git a/packages/loaders/test/resources/atlas.json b/packages/loaders/test/resources/atlas.json deleted file mode 100644 index 86e65a5..0000000 --- a/packages/loaders/test/resources/atlas.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas.png b/packages/loaders/test/resources/atlas.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/atlas@0.5x.json b/packages/loaders/test/resources/atlas@0.5x.json deleted file mode 100644 index ae990a1..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas@0.5x.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas@0.5x.png b/packages/loaders/test/resources/atlas@0.5x.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font.fnt b/packages/loaders/test/resources/font.fnt deleted file mode 100644 index 56e1060..0000000 --- a/packages/loaders/test/resources/font.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font.png b/packages/loaders/test/resources/font.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font@0.5x.fnt b/packages/loaders/test/resources/font@0.5x.fnt deleted file mode 100644 index 6c247c7..0000000 --- a/packages/loaders/test/resources/font@0.5x.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font@0.5x.png b/packages/loaders/test/resources/font@0.5x.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/spritesheetParser.js b/packages/loaders/test/spritesheetParser.js deleted file mode 100644 index 98890b8..0000000 --- a/packages/loaders/test/spritesheetParser.js +++ /dev/null @@ -1,213 +0,0 @@ -const path = require('path'); -const { spritesheetParser, Resource, Loader, getResourcePath } = require('../'); -const { Texture, BaseTexture } = require('@pixi/core'); - -describe('PIXI.loaders.spritesheetParser', function () -{ - it('should exist and return a function', function () - { - expect(spritesheetParser).to.be.a('function'); - expect(spritesheetParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not JSON', function () - { - const spy = sinon.spy(); - const res = {}; - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is JSON, but improper format', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, {}); - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should load the image & create textures if json is properly formatted', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); - const loader = new Loader(); - const addStub = sinon.stub(loader, 'add'); - const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); - - imgRes.texture = new Texture(new BaseTexture(imgRes.data)); - - addStub.yields(imgRes); - - spritesheetParser().call(loader, res, spy); - - addStub.restore(); - - expect(spy).to.have.been.calledOnce; - expect(addStub).to.have.been.calledWith( - `${res.name}_image`, - `${path.dirname(res.url)}/${res.data.meta.image}` - ); - expect(res).to.have.property('textures') - .that.is.an('object') - .with.keys(Object.keys(getJsonSpritesheet().frames)) - .and.has.property('0.png') - .that.is.an.instanceof(Texture); - }); - - it('should build the image url', function () - { - function getPath(url, image) - { - return getResourcePath({ - url, - data: { meta: { image } }, - }); - } - - let result = getPath('http://some.com/spritesheet.json', 'img.png'); - - expect(result).to.be.equals('http://some.com/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('http://some.com/some/img.png'); - - result = getPath('/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/img.png'); - - result = getPath('/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('/some/img.png'); - }); - - // TODO: Test that rectangles are created correctly. - // TODO: Test that bathc processing works correctly. - // TODO: Test that resolution processing works correctly. - // TODO: Test that metadata is honored. -}); - -function createMockResource(type, data) -{ - const name = `${Math.floor(Date.now() * Math.random())}`; - - return { - url: `http://localhost/doesnt_exist/${name}`, - name, - type, - data, - metadata: {}, - }; -} - -function getJsonSpritesheet() -{ - /* eslint-disable */ - return {"frames": { - "0.png": - { - "frame": {"x":14,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "1.png": - { - "frame": {"x":14,"y":42,"w":12,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, - "sourceSize": {"w":12,"h":14} - }, - "2.png": - { - "frame": {"x":14,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "3.png": - { - "frame": {"x":42,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "4.png": - { - "frame": {"x":28,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "5.png": - { - "frame": {"x":14,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "6.png": - { - "frame": {"x":0,"y":42,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "7.png": - { - "frame": {"x":0,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "8.png": - { - "frame": {"x":0,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "9.png": - { - "frame": {"x":0,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }}, - "meta": { - "app": "http://www.texturepacker.com", - "version": "1.0", - "image": "hud.png", - "format": "RGBA8888", - "size": {"w":64,"h":64}, - "scale": "1", - "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" - } - }; - /* eslint-enable */ -} diff --git a/packages/mixin-app-loader/LICENSE b/packages/mixin-app-loader/LICENSE new file mode 100644 index 0000000..5d16b50 --- /dev/null +++ b/packages/mixin-app-loader/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2013-2017 Mathew Groves, Chad Engler + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/packages/mixin-app-loader/README.md b/packages/mixin-app-loader/README.md new file mode 100644 index 0000000..3e89f86 --- /dev/null +++ b/packages/mixin-app-loader/README.md @@ -0,0 +1,13 @@ +# @pixi/mixin-app-loader + +## Installation + +```bash +npm install @pixi/mixin-app-loader +``` + +## Usage + +```js +import '@pixi/mixin-app-loader'; +``` \ No newline at end of file diff --git a/packages/mixin-app-loader/package.json b/packages/mixin-app-loader/package.json new file mode 100644 index 0000000..869ed4e --- /dev/null +++ b/packages/mixin-app-loader/package.json @@ -0,0 +1,41 @@ +{ + "name": "@pixi/mixin-app-loader", + "version": "5.0.0-alpha", + "main": "lib/mixin-app-loader.js", + "module": "lib/mixin-app-loader.es.js", + "description": "Support for loader in Application", + "author": "Mat Groves", + "contributors": [ + "Matt Karl " + ], + "homepage": "http://pixijs.com/", + "bugs": "https://github.com/pixijs/pixi.js/issues", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/pixijs/pixi.js.git" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "build:dev": "rollup -c", + "build": "rollup -cp", + "watch": "rollup -cw", + "postversion": "npm run build", + "test": "tester" + }, + "files": [ + "lib" + ], + "dependencies": { + "@pixi/app": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" + }, + "devDependencies": { + "@pixi/utils": "^5.0.0-alpha", + "@internal/tester": "^5.0.0-alpha", + "@internal/builder": "^5.0.0-alpha", + "rollup": "^0.50.0" + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/rollup.config.js b/packages/mixin-app-loader/rollup.config.js new file mode 100644 index 0000000..a73c874 --- /dev/null +++ b/packages/mixin-app-loader/rollup.config.js @@ -0,0 +1 @@ +export { default } from '@internal/builder'; diff --git a/packages/mixin-app-loader/src/index.js b/packages/mixin-app-loader/src/index.js new file mode 100644 index 0000000..354997a --- /dev/null +++ b/packages/mixin-app-loader/src/index.js @@ -0,0 +1,38 @@ +import { Application } from '@pixi/app'; +import { Loader, shared } from '@pixi/loaders'; + +Application.prototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperties(Application.prototype, { + loader: { + get() + { + if (!this._loader && this._options) + { + const { sharedLoader } = this._options; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +Application.prototype._parentDestroy = Application.prototype.destroy; +Application.prototype.destroy = function destroy(removeView) +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(removeView); +}; diff --git a/packages/mixin-app-loader/test/.eslintrc.json b/packages/mixin-app-loader/test/.eslintrc.json new file mode 100644 index 0000000..2094b04 --- /dev/null +++ b/packages/mixin-app-loader/test/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "globals": { + "expect": false, + "assert": false, + "sinon": false, + "PIXI": false + }, + "rules": { + "func-names": 0, + "no-unused-expressions": 0 + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/test/index.js b/packages/mixin-app-loader/test/index.js new file mode 100644 index 0000000..3468fe5 --- /dev/null +++ b/packages/mixin-app-loader/test/index.js @@ -0,0 +1,35 @@ +const { Application } = require('@pixi/app'); +const { Loader, shared } = require('@pixi/loaders'); +const { skipHello } = require('@pixi/utils'); + +require('../'); + +skipHello(); + +describe('PIXI.Application#loader', function () +{ + it('should contain loader property', function () + { + const obj = new Application(); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); + + it('should use sharedLoader option', function () + { + const obj = new Application({ sharedLoader: true }); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + expect(obj.loader).to.equal(shared); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); +}); diff --git a/packages/spritesheet/package.json b/packages/spritesheet/package.json index 34749e2..5f35442 100644 --- a/packages/spritesheet/package.json +++ b/packages/spritesheet/package.json @@ -31,7 +31,8 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/math": "^5.0.0-alpha", - "@pixi/utils": "^5.0.0-alpha" + "@pixi/utils": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" }, "devDependencies": { "@internal/builder": "^5.0.0-alpha", diff --git a/packages/spritesheet/src/SpritesheetLoader.js b/packages/spritesheet/src/SpritesheetLoader.js new file mode 100644 index 0000000..c7b34ea --- /dev/null +++ b/packages/spritesheet/src/SpritesheetLoader.js @@ -0,0 +1,83 @@ +import url from 'url'; +import { Resource, Loader } from '@pixi/loaders'; +import Spritesheet from './Spritesheet'; + +/** + * {@link PIXI.loaders.Loader Loader} middleware for loading + * texture atlases that have been created with TexturePacker or + * similar JSON-based spritesheet. This automatically generates + * Texture resources. + * @class + * @memberof PIXI + */ +export default class SpritesheetLoader +{ + /** + * Middleware function to support Spritesheets, this is automatically installed. + * @see PIXI.loaders.Loader.useMiddleware + */ + static middleware() + { + return function spritesheetLoader(resource, next) + { + const imageResourceName = `${resource.name}_image`; + + // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists + if (!resource.data + || resource.type !== Resource.TYPE.JSON + || !resource.data.frames + || this.resources[imageResourceName] + ) + { + next(); + + return; + } + + const loadOptions = { + crossOrigin: resource.crossOrigin, + loadType: Resource.LOAD_TYPE.IMAGE, + metadata: resource.metadata.imageMetadata, + parentResource: resource, + }; + + const resourcePath = SpritesheetLoader.getResourcePath(resource, this.baseUrl); + + // load the image for this sheet + this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) + { + const spritesheet = new Spritesheet( + res.texture.baseTexture, + resource.data, + resource.url + ); + + spritesheet.parse(() => + { + resource.spritesheet = spritesheet; + resource.textures = spritesheet.textures; + next(); + }); + }); + }; + } + + /** + * Get the spritesheets root path + * @param {PIXI.loader.Resource} resource - Resource to check path + * @param {string} baseUrl - Base root url + */ + static getResourcePath(resource, baseUrl) + { + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); + } +} + +// Install loader support for Spritesheet objects +Loader.useMiddleware(SpritesheetLoader.middleware); diff --git a/packages/spritesheet/src/index.js b/packages/spritesheet/src/index.js index 51d96a1..b9ee1e8 100644 --- a/packages/spritesheet/src/index.js +++ b/packages/spritesheet/src/index.js @@ -1 +1,2 @@ export { default as Spritesheet } from './Spritesheet'; +export { default as SpritesheetLoader } from './SpritesheetLoader'; diff --git a/packages/spritesheet/test/Spritesheet.js b/packages/spritesheet/test/Spritesheet.js new file mode 100644 index 0000000..35d8688 --- /dev/null +++ b/packages/spritesheet/test/Spritesheet.js @@ -0,0 +1,105 @@ +const { Spritesheet } = require('../'); +const { BaseTexture, Texture } = require('@pixi/core'); +const path = require('path'); + +describe('PIXI.Spritesheet', function () +{ + before(function () + { + this.resources = path.join(__dirname, 'resources'); + this.validate = function (spritesheet, done) + { + spritesheet.parse(function (textures) + { + const id = 'goldmine_10_5.png'; + const width = Math.floor(spritesheet.data.frames[id].frame.w); + const height = Math.floor(spritesheet.data.frames[id].frame.h); + + expect(Object.keys(textures).length).to.equal(1); + expect(Object.keys(spritesheet.textures).length).to.equal(1); + expect(textures[id]).to.be.an.instanceof(Texture); + expect(textures[id].width).to.equal(width / spritesheet.resolution); + expect(textures[id].height).to.equal(height / spritesheet.resolution); + expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); + spritesheet.destroy(true); + expect(spritesheet.textures).to.be.null; + expect(spritesheet.baseTexture).to.be.null; + done(); + }); + }; + }); + + it('should exist on PIXI', function () + { + expect(Spritesheet).to.be.a.function; + expect(Spritesheet.BATCH_SIZE).to.be.a.number; + }); + + it('should create an instance', function () + { + const baseTexture = new BaseTexture(); + const data = { + frames: {}, + meta: {}, + }; + + const spritesheet = new Spritesheet(baseTexture, data); + + expect(spritesheet.data).to.equal(data); + expect(spritesheet.baseTexture).to.equal(baseTexture); + expect(spritesheet.resolution).to.equal(1); + + spritesheet.destroy(true); + }); + + it('should create instance with scale resolution', function (done) + { + const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require + const image = new Image(); + + image.src = path.join(this.resources, data.meta.image); + image.onload = () => + { + const baseTexture = new BaseTexture(image, null, 1); + const spritesheet = new Spritesheet(baseTexture, data); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1.png'); + expect(spritesheet.resolution).to.equal(0.5); + this.validate(spritesheet, done); + }; + }); + + it('should create instance with BaseTexture source scale', function (done) + { + const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require + const baseTexture = new BaseTexture.fromImage(data.meta.image, undefined, undefined, 1.5); + const spritesheet = new Spritesheet(baseTexture, data); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1.png'); + expect(spritesheet.resolution).to.equal(0.5); + + this.validate(spritesheet, done); + }); + + it('should create instance with filename resolution', function (done) + { + const uri = path.resolve(this.resources, 'building1@2x.json'); + const data = require(uri); // eslint-disable-line global-require + const image = new Image(); + + image.src = path.join(this.resources, data.meta.image); + image.onload = () => + { + const baseTexture = new BaseTexture(image, null, 1); + const spritesheet = new Spritesheet(baseTexture, data, uri); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1@2x.png'); + expect(spritesheet.resolution).to.equal(2); + + this.validate(spritesheet, done); + }; + }); +}); diff --git a/packages/spritesheet/test/SpritesheetLoader.js b/packages/spritesheet/test/SpritesheetLoader.js new file mode 100644 index 0000000..e4205ec --- /dev/null +++ b/packages/spritesheet/test/SpritesheetLoader.js @@ -0,0 +1,214 @@ +const path = require('path'); +const { Loader, Resource } = require('@pixi/loaders'); +const { Texture, BaseTexture } = require('@pixi/core'); +const { SpritesheetLoader } = require('../'); + +describe('PIXI.SpritesheetLoader', function () +{ + it('should exist and return a function', function () + { + expect(SpritesheetLoader.middleware).to.be.a('function'); + expect(SpritesheetLoader.middleware()).to.be.a('function'); + }); + + it('should do nothing if the resource is not JSON', function () + { + const spy = sinon.spy(); + const res = {}; + + SpritesheetLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should do nothing if the resource is JSON, but improper format', function () + { + const spy = sinon.spy(); + const res = createMockResource(Resource.TYPE.JSON, {}); + + SpritesheetLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should load the image & create textures if json is properly formatted', function () + { + const spy = sinon.spy(); + const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); + const loader = new Loader(); + const addStub = sinon.stub(loader, 'add'); + const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); + + imgRes.texture = new Texture(new BaseTexture(imgRes.data)); + + addStub.yields(imgRes); + + SpritesheetLoader.middleware().call(loader, res, spy); + + addStub.restore(); + + expect(spy).to.have.been.calledOnce; + expect(addStub).to.have.been.calledWith( + `${res.name}_image`, + `${path.dirname(res.url)}/${res.data.meta.image}` + ); + expect(res).to.have.property('textures') + .that.is.an('object') + .with.keys(Object.keys(getJsonSpritesheet().frames)) + .and.has.property('0.png') + .that.is.an.instanceof(Texture); + }); + + it('should build the image url', function () + { + function getPath(url, image) + { + return SpritesheetLoader.getResourcePath({ + url, + data: { meta: { image } }, + }); + } + + let result = getPath('http://some.com/spritesheet.json', 'img.png'); + + expect(result).to.be.equals('http://some.com/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); + expect(result).to.be.equals('http://some.com/some/dir/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); + expect(result).to.be.equals('http://some.com/some/dir/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); + expect(result).to.be.equals('http://some.com/some/img.png'); + + result = getPath('/spritesheet.json', 'img.png'); + expect(result).to.be.equals('/img.png'); + + result = getPath('/some/dir/spritesheet.json', 'img.png'); + expect(result).to.be.equals('/some/dir/img.png'); + + result = getPath('/some/dir/spritesheet.json', './img.png'); + expect(result).to.be.equals('/some/dir/img.png'); + + result = getPath('/some/dir/spritesheet.json', '../img.png'); + expect(result).to.be.equals('/some/img.png'); + }); + + // TODO: Test that rectangles are created correctly. + // TODO: Test that bathc processing works correctly. + // TODO: Test that resolution processing works correctly. + // TODO: Test that metadata is honored. +}); + +function createMockResource(type, data) +{ + const name = `${Math.floor(Date.now() * Math.random())}`; + + return { + url: `http://localhost/doesnt_exist/${name}`, + name, + type, + data, + metadata: {}, + }; +} + +function getJsonSpritesheet() +{ + /* eslint-disable */ + return {"frames": { + "0.png": + { + "frame": {"x":14,"y":28,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "1.png": + { + "frame": {"x":14,"y":42,"w":12,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, + "sourceSize": {"w":12,"h":14} + }, + "2.png": + { + "frame": {"x":14,"y":14,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "3.png": + { + "frame": {"x":42,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "4.png": + { + "frame": {"x":28,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "5.png": + { + "frame": {"x":14,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "6.png": + { + "frame": {"x":0,"y":42,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "7.png": + { + "frame": {"x":0,"y":28,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "8.png": + { + "frame": {"x":0,"y":14,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "9.png": + { + "frame": {"x":0,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }}, + "meta": { + "app": "http://www.texturepacker.com", + "version": "1.0", + "image": "hud.png", + "format": "RGBA8888", + "size": {"w":64,"h":64}, + "scale": "1", + "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" + } + }; + /* eslint-enable */ +} diff --git a/packages/spritesheet/test/index.js b/packages/spritesheet/test/index.js index 35d8688..8cf5636 100644 --- a/packages/spritesheet/test/index.js +++ b/packages/spritesheet/test/index.js @@ -1,105 +1,2 @@ -const { Spritesheet } = require('../'); -const { BaseTexture, Texture } = require('@pixi/core'); -const path = require('path'); - -describe('PIXI.Spritesheet', function () -{ - before(function () - { - this.resources = path.join(__dirname, 'resources'); - this.validate = function (spritesheet, done) - { - spritesheet.parse(function (textures) - { - const id = 'goldmine_10_5.png'; - const width = Math.floor(spritesheet.data.frames[id].frame.w); - const height = Math.floor(spritesheet.data.frames[id].frame.h); - - expect(Object.keys(textures).length).to.equal(1); - expect(Object.keys(spritesheet.textures).length).to.equal(1); - expect(textures[id]).to.be.an.instanceof(Texture); - expect(textures[id].width).to.equal(width / spritesheet.resolution); - expect(textures[id].height).to.equal(height / spritesheet.resolution); - expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); - spritesheet.destroy(true); - expect(spritesheet.textures).to.be.null; - expect(spritesheet.baseTexture).to.be.null; - done(); - }); - }; - }); - - it('should exist on PIXI', function () - { - expect(Spritesheet).to.be.a.function; - expect(Spritesheet.BATCH_SIZE).to.be.a.number; - }); - - it('should create an instance', function () - { - const baseTexture = new BaseTexture(); - const data = { - frames: {}, - meta: {}, - }; - - const spritesheet = new Spritesheet(baseTexture, data); - - expect(spritesheet.data).to.equal(data); - expect(spritesheet.baseTexture).to.equal(baseTexture); - expect(spritesheet.resolution).to.equal(1); - - spritesheet.destroy(true); - }); - - it('should create instance with scale resolution', function (done) - { - const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require - const image = new Image(); - - image.src = path.join(this.resources, data.meta.image); - image.onload = () => - { - const baseTexture = new BaseTexture(image, null, 1); - const spritesheet = new Spritesheet(baseTexture, data); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1.png'); - expect(spritesheet.resolution).to.equal(0.5); - this.validate(spritesheet, done); - }; - }); - - it('should create instance with BaseTexture source scale', function (done) - { - const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require - const baseTexture = new BaseTexture.fromImage(data.meta.image, undefined, undefined, 1.5); - const spritesheet = new Spritesheet(baseTexture, data); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1.png'); - expect(spritesheet.resolution).to.equal(0.5); - - this.validate(spritesheet, done); - }); - - it('should create instance with filename resolution', function (done) - { - const uri = path.resolve(this.resources, 'building1@2x.json'); - const data = require(uri); // eslint-disable-line global-require - const image = new Image(); - - image.src = path.join(this.resources, data.meta.image); - image.onload = () => - { - const baseTexture = new BaseTexture(image, null, 1); - const spritesheet = new Spritesheet(baseTexture, data, uri); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1@2x.png'); - expect(spritesheet.resolution).to.equal(2); - - this.validate(spritesheet, done); - }; - }); -}); +require('./Spritesheet'); +require('./SpritesheetLoader'); diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/packages/loaders/src/spritesheetParser.js b/packages/loaders/src/spritesheetParser.js deleted file mode 100644 index f7fa206..0000000 --- a/packages/loaders/src/spritesheetParser.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Resource } from 'resource-loader'; -import url from 'url'; -import { Spritesheet } from '@pixi/spritesheet'; - -export default function () -{ - return function spritesheetParser(resource, next) - { - const imageResourceName = `${resource.name}_image`; - - // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists - if (!resource.data - || resource.type !== Resource.TYPE.JSON - || !resource.data.frames - || this.resources[imageResourceName] - ) - { - next(); - - return; - } - - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - const resourcePath = getResourcePath(resource, this.baseUrl); - - // load the image for this sheet - this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) - { - const spritesheet = new Spritesheet( - res.texture.baseTexture, - resource.data, - resource.url - ); - - spritesheet.parse(() => - { - resource.spritesheet = spritesheet; - resource.textures = spritesheet.textures; - next(); - }); - }); - }; -} - -export function getResourcePath(resource, baseUrl) -{ - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - return resource.data.meta.image; - } - - return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); -} diff --git a/packages/loaders/test/bitmapFontParser.js b/packages/loaders/test/bitmapFontParser.js deleted file mode 100644 index 43ef194..0000000 --- a/packages/loaders/test/bitmapFontParser.js +++ /dev/null @@ -1,325 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const { BitmapText } = require('@pixi/text-bitmap'); -const { BaseTextureCache, TextureCache } = require('@pixi/utils'); -const { Texture, BaseTexture } = require('@pixi/core'); -const { Spritesheet } = require('@pixi/spritesheet'); -const { bitmapFontParser, parseBitmapFontData } = require('../'); - -describe('PIXI.loaders.bitmapFontParser', function () -{ - afterEach(function () - { - for (const font in BitmapText.fonts) - { - delete BitmapText.fonts[font]; - } - for (const baseTexture in BaseTextureCache) - { - delete BaseTextureCache[baseTexture]; - } - for (const texture in TextureCache) - { - delete TextureCache[texture]; - } - }); - - before(function (done) - { - const resolveURL = (url) => path.resolve(this.resources, url); - - this.resources = path.join(__dirname, 'resources'); - this.fontXML = null; - this.fontScaledXML = null; - this.fontImage = null; - this.fontScaledImage = null; - this.atlasImage = null; - this.atlasScaledImage = null; - this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require - this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require - - const loadXML = (url) => new Promise((resolve) => - fs.readFile(resolveURL(url), 'utf8', (err, data) => - { - expect(err).to.be.null; - resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); - })); - - const loadImage = (url) => new Promise((resolve) => - { - const image = new Image(); - - image.onload = () => resolve(image); - image.src = resolveURL(url); - }); - - Promise.all([ - loadXML('font.fnt'), - loadXML('font@0.5x.fnt'), - loadImage('font.png'), - loadImage('font@0.5x.png'), - loadImage('atlas.png'), - loadImage('atlas@0.5x.png'), - ]).then(([ - fontXML, - fontScaledXML, - fontImage, - fontScaledImage, - atlasImage, - atlasScaledImage, - ]) => - { - this.fontXML = fontXML; - this.fontScaledXML = fontScaledXML; - this.fontImage = fontImage; - this.fontScaledImage = fontScaledImage; - this.atlasImage = atlasImage; - this.atlasScaledImage = atlasScaledImage; - done(); - }); - }); - - it('should exist and return a function', function () - { - expect(bitmapFontParser).to.be.a('function'); - expect(bitmapFontParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not XML', function () - { - const spy = sinon.spy(); - const res = {}; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is not properly formatted XML', function () - { - const spy = sinon.spy(); - const res = { data: document.createDocumentFragment() }; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - // TODO: Test the texture cache code path. - // TODO: Test the loading texture code path. - // TODO: Test data-url code paths. - - it('should properly register bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charA.texture.frame.x).to.equal(2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charB.texture.frame.x).to.equal(2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charC.texture.frame.x).to.equal(23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charD.texture.frame.x).to.equal(19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register SCALED bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); - const font = BitmapText.registerFont(this.fontScaledXML, 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register bitmap font NESTED into spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); - - it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); - - spritesheet.resolution = 1; - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); -}); - -describe('PIXI.loaders.parseBitmapFontData', function () -{ - it('should exist', function () - { - expect(parseBitmapFontData).to.be.a('function'); - }); - - // TODO: Test the parser code. -}); diff --git a/packages/loaders/test/index.js b/packages/loaders/test/index.js index f00de5f..72c6554 100644 --- a/packages/loaders/test/index.js +++ b/packages/loaders/test/index.js @@ -1,4 +1,2 @@ -require('./bitmapFontParser'); require('./loader'); -require('./spritesheetParser'); require('./textureParser'); diff --git a/packages/loaders/test/resources/atlas.json b/packages/loaders/test/resources/atlas.json deleted file mode 100644 index 86e65a5..0000000 --- a/packages/loaders/test/resources/atlas.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas.png b/packages/loaders/test/resources/atlas.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/atlas@0.5x.json b/packages/loaders/test/resources/atlas@0.5x.json deleted file mode 100644 index ae990a1..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas@0.5x.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas@0.5x.png b/packages/loaders/test/resources/atlas@0.5x.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font.fnt b/packages/loaders/test/resources/font.fnt deleted file mode 100644 index 56e1060..0000000 --- a/packages/loaders/test/resources/font.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font.png b/packages/loaders/test/resources/font.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font@0.5x.fnt b/packages/loaders/test/resources/font@0.5x.fnt deleted file mode 100644 index 6c247c7..0000000 --- a/packages/loaders/test/resources/font@0.5x.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font@0.5x.png b/packages/loaders/test/resources/font@0.5x.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/spritesheetParser.js b/packages/loaders/test/spritesheetParser.js deleted file mode 100644 index 98890b8..0000000 --- a/packages/loaders/test/spritesheetParser.js +++ /dev/null @@ -1,213 +0,0 @@ -const path = require('path'); -const { spritesheetParser, Resource, Loader, getResourcePath } = require('../'); -const { Texture, BaseTexture } = require('@pixi/core'); - -describe('PIXI.loaders.spritesheetParser', function () -{ - it('should exist and return a function', function () - { - expect(spritesheetParser).to.be.a('function'); - expect(spritesheetParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not JSON', function () - { - const spy = sinon.spy(); - const res = {}; - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is JSON, but improper format', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, {}); - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should load the image & create textures if json is properly formatted', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); - const loader = new Loader(); - const addStub = sinon.stub(loader, 'add'); - const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); - - imgRes.texture = new Texture(new BaseTexture(imgRes.data)); - - addStub.yields(imgRes); - - spritesheetParser().call(loader, res, spy); - - addStub.restore(); - - expect(spy).to.have.been.calledOnce; - expect(addStub).to.have.been.calledWith( - `${res.name}_image`, - `${path.dirname(res.url)}/${res.data.meta.image}` - ); - expect(res).to.have.property('textures') - .that.is.an('object') - .with.keys(Object.keys(getJsonSpritesheet().frames)) - .and.has.property('0.png') - .that.is.an.instanceof(Texture); - }); - - it('should build the image url', function () - { - function getPath(url, image) - { - return getResourcePath({ - url, - data: { meta: { image } }, - }); - } - - let result = getPath('http://some.com/spritesheet.json', 'img.png'); - - expect(result).to.be.equals('http://some.com/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('http://some.com/some/img.png'); - - result = getPath('/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/img.png'); - - result = getPath('/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('/some/img.png'); - }); - - // TODO: Test that rectangles are created correctly. - // TODO: Test that bathc processing works correctly. - // TODO: Test that resolution processing works correctly. - // TODO: Test that metadata is honored. -}); - -function createMockResource(type, data) -{ - const name = `${Math.floor(Date.now() * Math.random())}`; - - return { - url: `http://localhost/doesnt_exist/${name}`, - name, - type, - data, - metadata: {}, - }; -} - -function getJsonSpritesheet() -{ - /* eslint-disable */ - return {"frames": { - "0.png": - { - "frame": {"x":14,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "1.png": - { - "frame": {"x":14,"y":42,"w":12,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, - "sourceSize": {"w":12,"h":14} - }, - "2.png": - { - "frame": {"x":14,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "3.png": - { - "frame": {"x":42,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "4.png": - { - "frame": {"x":28,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "5.png": - { - "frame": {"x":14,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "6.png": - { - "frame": {"x":0,"y":42,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "7.png": - { - "frame": {"x":0,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "8.png": - { - "frame": {"x":0,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "9.png": - { - "frame": {"x":0,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }}, - "meta": { - "app": "http://www.texturepacker.com", - "version": "1.0", - "image": "hud.png", - "format": "RGBA8888", - "size": {"w":64,"h":64}, - "scale": "1", - "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" - } - }; - /* eslint-enable */ -} diff --git a/packages/mixin-app-loader/LICENSE b/packages/mixin-app-loader/LICENSE new file mode 100644 index 0000000..5d16b50 --- /dev/null +++ b/packages/mixin-app-loader/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2013-2017 Mathew Groves, Chad Engler + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/packages/mixin-app-loader/README.md b/packages/mixin-app-loader/README.md new file mode 100644 index 0000000..3e89f86 --- /dev/null +++ b/packages/mixin-app-loader/README.md @@ -0,0 +1,13 @@ +# @pixi/mixin-app-loader + +## Installation + +```bash +npm install @pixi/mixin-app-loader +``` + +## Usage + +```js +import '@pixi/mixin-app-loader'; +``` \ No newline at end of file diff --git a/packages/mixin-app-loader/package.json b/packages/mixin-app-loader/package.json new file mode 100644 index 0000000..869ed4e --- /dev/null +++ b/packages/mixin-app-loader/package.json @@ -0,0 +1,41 @@ +{ + "name": "@pixi/mixin-app-loader", + "version": "5.0.0-alpha", + "main": "lib/mixin-app-loader.js", + "module": "lib/mixin-app-loader.es.js", + "description": "Support for loader in Application", + "author": "Mat Groves", + "contributors": [ + "Matt Karl " + ], + "homepage": "http://pixijs.com/", + "bugs": "https://github.com/pixijs/pixi.js/issues", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/pixijs/pixi.js.git" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "build:dev": "rollup -c", + "build": "rollup -cp", + "watch": "rollup -cw", + "postversion": "npm run build", + "test": "tester" + }, + "files": [ + "lib" + ], + "dependencies": { + "@pixi/app": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" + }, + "devDependencies": { + "@pixi/utils": "^5.0.0-alpha", + "@internal/tester": "^5.0.0-alpha", + "@internal/builder": "^5.0.0-alpha", + "rollup": "^0.50.0" + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/rollup.config.js b/packages/mixin-app-loader/rollup.config.js new file mode 100644 index 0000000..a73c874 --- /dev/null +++ b/packages/mixin-app-loader/rollup.config.js @@ -0,0 +1 @@ +export { default } from '@internal/builder'; diff --git a/packages/mixin-app-loader/src/index.js b/packages/mixin-app-loader/src/index.js new file mode 100644 index 0000000..354997a --- /dev/null +++ b/packages/mixin-app-loader/src/index.js @@ -0,0 +1,38 @@ +import { Application } from '@pixi/app'; +import { Loader, shared } from '@pixi/loaders'; + +Application.prototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperties(Application.prototype, { + loader: { + get() + { + if (!this._loader && this._options) + { + const { sharedLoader } = this._options; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +Application.prototype._parentDestroy = Application.prototype.destroy; +Application.prototype.destroy = function destroy(removeView) +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(removeView); +}; diff --git a/packages/mixin-app-loader/test/.eslintrc.json b/packages/mixin-app-loader/test/.eslintrc.json new file mode 100644 index 0000000..2094b04 --- /dev/null +++ b/packages/mixin-app-loader/test/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "globals": { + "expect": false, + "assert": false, + "sinon": false, + "PIXI": false + }, + "rules": { + "func-names": 0, + "no-unused-expressions": 0 + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/test/index.js b/packages/mixin-app-loader/test/index.js new file mode 100644 index 0000000..3468fe5 --- /dev/null +++ b/packages/mixin-app-loader/test/index.js @@ -0,0 +1,35 @@ +const { Application } = require('@pixi/app'); +const { Loader, shared } = require('@pixi/loaders'); +const { skipHello } = require('@pixi/utils'); + +require('../'); + +skipHello(); + +describe('PIXI.Application#loader', function () +{ + it('should contain loader property', function () + { + const obj = new Application(); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); + + it('should use sharedLoader option', function () + { + const obj = new Application({ sharedLoader: true }); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + expect(obj.loader).to.equal(shared); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); +}); diff --git a/packages/spritesheet/package.json b/packages/spritesheet/package.json index 34749e2..5f35442 100644 --- a/packages/spritesheet/package.json +++ b/packages/spritesheet/package.json @@ -31,7 +31,8 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/math": "^5.0.0-alpha", - "@pixi/utils": "^5.0.0-alpha" + "@pixi/utils": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" }, "devDependencies": { "@internal/builder": "^5.0.0-alpha", diff --git a/packages/spritesheet/src/SpritesheetLoader.js b/packages/spritesheet/src/SpritesheetLoader.js new file mode 100644 index 0000000..c7b34ea --- /dev/null +++ b/packages/spritesheet/src/SpritesheetLoader.js @@ -0,0 +1,83 @@ +import url from 'url'; +import { Resource, Loader } from '@pixi/loaders'; +import Spritesheet from './Spritesheet'; + +/** + * {@link PIXI.loaders.Loader Loader} middleware for loading + * texture atlases that have been created with TexturePacker or + * similar JSON-based spritesheet. This automatically generates + * Texture resources. + * @class + * @memberof PIXI + */ +export default class SpritesheetLoader +{ + /** + * Middleware function to support Spritesheets, this is automatically installed. + * @see PIXI.loaders.Loader.useMiddleware + */ + static middleware() + { + return function spritesheetLoader(resource, next) + { + const imageResourceName = `${resource.name}_image`; + + // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists + if (!resource.data + || resource.type !== Resource.TYPE.JSON + || !resource.data.frames + || this.resources[imageResourceName] + ) + { + next(); + + return; + } + + const loadOptions = { + crossOrigin: resource.crossOrigin, + loadType: Resource.LOAD_TYPE.IMAGE, + metadata: resource.metadata.imageMetadata, + parentResource: resource, + }; + + const resourcePath = SpritesheetLoader.getResourcePath(resource, this.baseUrl); + + // load the image for this sheet + this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) + { + const spritesheet = new Spritesheet( + res.texture.baseTexture, + resource.data, + resource.url + ); + + spritesheet.parse(() => + { + resource.spritesheet = spritesheet; + resource.textures = spritesheet.textures; + next(); + }); + }); + }; + } + + /** + * Get the spritesheets root path + * @param {PIXI.loader.Resource} resource - Resource to check path + * @param {string} baseUrl - Base root url + */ + static getResourcePath(resource, baseUrl) + { + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); + } +} + +// Install loader support for Spritesheet objects +Loader.useMiddleware(SpritesheetLoader.middleware); diff --git a/packages/spritesheet/src/index.js b/packages/spritesheet/src/index.js index 51d96a1..b9ee1e8 100644 --- a/packages/spritesheet/src/index.js +++ b/packages/spritesheet/src/index.js @@ -1 +1,2 @@ export { default as Spritesheet } from './Spritesheet'; +export { default as SpritesheetLoader } from './SpritesheetLoader'; diff --git a/packages/spritesheet/test/Spritesheet.js b/packages/spritesheet/test/Spritesheet.js new file mode 100644 index 0000000..35d8688 --- /dev/null +++ b/packages/spritesheet/test/Spritesheet.js @@ -0,0 +1,105 @@ +const { Spritesheet } = require('../'); +const { BaseTexture, Texture } = require('@pixi/core'); +const path = require('path'); + +describe('PIXI.Spritesheet', function () +{ + before(function () + { + this.resources = path.join(__dirname, 'resources'); + this.validate = function (spritesheet, done) + { + spritesheet.parse(function (textures) + { + const id = 'goldmine_10_5.png'; + const width = Math.floor(spritesheet.data.frames[id].frame.w); + const height = Math.floor(spritesheet.data.frames[id].frame.h); + + expect(Object.keys(textures).length).to.equal(1); + expect(Object.keys(spritesheet.textures).length).to.equal(1); + expect(textures[id]).to.be.an.instanceof(Texture); + expect(textures[id].width).to.equal(width / spritesheet.resolution); + expect(textures[id].height).to.equal(height / spritesheet.resolution); + expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); + spritesheet.destroy(true); + expect(spritesheet.textures).to.be.null; + expect(spritesheet.baseTexture).to.be.null; + done(); + }); + }; + }); + + it('should exist on PIXI', function () + { + expect(Spritesheet).to.be.a.function; + expect(Spritesheet.BATCH_SIZE).to.be.a.number; + }); + + it('should create an instance', function () + { + const baseTexture = new BaseTexture(); + const data = { + frames: {}, + meta: {}, + }; + + const spritesheet = new Spritesheet(baseTexture, data); + + expect(spritesheet.data).to.equal(data); + expect(spritesheet.baseTexture).to.equal(baseTexture); + expect(spritesheet.resolution).to.equal(1); + + spritesheet.destroy(true); + }); + + it('should create instance with scale resolution', function (done) + { + const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require + const image = new Image(); + + image.src = path.join(this.resources, data.meta.image); + image.onload = () => + { + const baseTexture = new BaseTexture(image, null, 1); + const spritesheet = new Spritesheet(baseTexture, data); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1.png'); + expect(spritesheet.resolution).to.equal(0.5); + this.validate(spritesheet, done); + }; + }); + + it('should create instance with BaseTexture source scale', function (done) + { + const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require + const baseTexture = new BaseTexture.fromImage(data.meta.image, undefined, undefined, 1.5); + const spritesheet = new Spritesheet(baseTexture, data); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1.png'); + expect(spritesheet.resolution).to.equal(0.5); + + this.validate(spritesheet, done); + }); + + it('should create instance with filename resolution', function (done) + { + const uri = path.resolve(this.resources, 'building1@2x.json'); + const data = require(uri); // eslint-disable-line global-require + const image = new Image(); + + image.src = path.join(this.resources, data.meta.image); + image.onload = () => + { + const baseTexture = new BaseTexture(image, null, 1); + const spritesheet = new Spritesheet(baseTexture, data, uri); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1@2x.png'); + expect(spritesheet.resolution).to.equal(2); + + this.validate(spritesheet, done); + }; + }); +}); diff --git a/packages/spritesheet/test/SpritesheetLoader.js b/packages/spritesheet/test/SpritesheetLoader.js new file mode 100644 index 0000000..e4205ec --- /dev/null +++ b/packages/spritesheet/test/SpritesheetLoader.js @@ -0,0 +1,214 @@ +const path = require('path'); +const { Loader, Resource } = require('@pixi/loaders'); +const { Texture, BaseTexture } = require('@pixi/core'); +const { SpritesheetLoader } = require('../'); + +describe('PIXI.SpritesheetLoader', function () +{ + it('should exist and return a function', function () + { + expect(SpritesheetLoader.middleware).to.be.a('function'); + expect(SpritesheetLoader.middleware()).to.be.a('function'); + }); + + it('should do nothing if the resource is not JSON', function () + { + const spy = sinon.spy(); + const res = {}; + + SpritesheetLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should do nothing if the resource is JSON, but improper format', function () + { + const spy = sinon.spy(); + const res = createMockResource(Resource.TYPE.JSON, {}); + + SpritesheetLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should load the image & create textures if json is properly formatted', function () + { + const spy = sinon.spy(); + const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); + const loader = new Loader(); + const addStub = sinon.stub(loader, 'add'); + const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); + + imgRes.texture = new Texture(new BaseTexture(imgRes.data)); + + addStub.yields(imgRes); + + SpritesheetLoader.middleware().call(loader, res, spy); + + addStub.restore(); + + expect(spy).to.have.been.calledOnce; + expect(addStub).to.have.been.calledWith( + `${res.name}_image`, + `${path.dirname(res.url)}/${res.data.meta.image}` + ); + expect(res).to.have.property('textures') + .that.is.an('object') + .with.keys(Object.keys(getJsonSpritesheet().frames)) + .and.has.property('0.png') + .that.is.an.instanceof(Texture); + }); + + it('should build the image url', function () + { + function getPath(url, image) + { + return SpritesheetLoader.getResourcePath({ + url, + data: { meta: { image } }, + }); + } + + let result = getPath('http://some.com/spritesheet.json', 'img.png'); + + expect(result).to.be.equals('http://some.com/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); + expect(result).to.be.equals('http://some.com/some/dir/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); + expect(result).to.be.equals('http://some.com/some/dir/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); + expect(result).to.be.equals('http://some.com/some/img.png'); + + result = getPath('/spritesheet.json', 'img.png'); + expect(result).to.be.equals('/img.png'); + + result = getPath('/some/dir/spritesheet.json', 'img.png'); + expect(result).to.be.equals('/some/dir/img.png'); + + result = getPath('/some/dir/spritesheet.json', './img.png'); + expect(result).to.be.equals('/some/dir/img.png'); + + result = getPath('/some/dir/spritesheet.json', '../img.png'); + expect(result).to.be.equals('/some/img.png'); + }); + + // TODO: Test that rectangles are created correctly. + // TODO: Test that bathc processing works correctly. + // TODO: Test that resolution processing works correctly. + // TODO: Test that metadata is honored. +}); + +function createMockResource(type, data) +{ + const name = `${Math.floor(Date.now() * Math.random())}`; + + return { + url: `http://localhost/doesnt_exist/${name}`, + name, + type, + data, + metadata: {}, + }; +} + +function getJsonSpritesheet() +{ + /* eslint-disable */ + return {"frames": { + "0.png": + { + "frame": {"x":14,"y":28,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "1.png": + { + "frame": {"x":14,"y":42,"w":12,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, + "sourceSize": {"w":12,"h":14} + }, + "2.png": + { + "frame": {"x":14,"y":14,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "3.png": + { + "frame": {"x":42,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "4.png": + { + "frame": {"x":28,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "5.png": + { + "frame": {"x":14,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "6.png": + { + "frame": {"x":0,"y":42,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "7.png": + { + "frame": {"x":0,"y":28,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "8.png": + { + "frame": {"x":0,"y":14,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "9.png": + { + "frame": {"x":0,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }}, + "meta": { + "app": "http://www.texturepacker.com", + "version": "1.0", + "image": "hud.png", + "format": "RGBA8888", + "size": {"w":64,"h":64}, + "scale": "1", + "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" + } + }; + /* eslint-enable */ +} diff --git a/packages/spritesheet/test/index.js b/packages/spritesheet/test/index.js index 35d8688..8cf5636 100644 --- a/packages/spritesheet/test/index.js +++ b/packages/spritesheet/test/index.js @@ -1,105 +1,2 @@ -const { Spritesheet } = require('../'); -const { BaseTexture, Texture } = require('@pixi/core'); -const path = require('path'); - -describe('PIXI.Spritesheet', function () -{ - before(function () - { - this.resources = path.join(__dirname, 'resources'); - this.validate = function (spritesheet, done) - { - spritesheet.parse(function (textures) - { - const id = 'goldmine_10_5.png'; - const width = Math.floor(spritesheet.data.frames[id].frame.w); - const height = Math.floor(spritesheet.data.frames[id].frame.h); - - expect(Object.keys(textures).length).to.equal(1); - expect(Object.keys(spritesheet.textures).length).to.equal(1); - expect(textures[id]).to.be.an.instanceof(Texture); - expect(textures[id].width).to.equal(width / spritesheet.resolution); - expect(textures[id].height).to.equal(height / spritesheet.resolution); - expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); - spritesheet.destroy(true); - expect(spritesheet.textures).to.be.null; - expect(spritesheet.baseTexture).to.be.null; - done(); - }); - }; - }); - - it('should exist on PIXI', function () - { - expect(Spritesheet).to.be.a.function; - expect(Spritesheet.BATCH_SIZE).to.be.a.number; - }); - - it('should create an instance', function () - { - const baseTexture = new BaseTexture(); - const data = { - frames: {}, - meta: {}, - }; - - const spritesheet = new Spritesheet(baseTexture, data); - - expect(spritesheet.data).to.equal(data); - expect(spritesheet.baseTexture).to.equal(baseTexture); - expect(spritesheet.resolution).to.equal(1); - - spritesheet.destroy(true); - }); - - it('should create instance with scale resolution', function (done) - { - const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require - const image = new Image(); - - image.src = path.join(this.resources, data.meta.image); - image.onload = () => - { - const baseTexture = new BaseTexture(image, null, 1); - const spritesheet = new Spritesheet(baseTexture, data); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1.png'); - expect(spritesheet.resolution).to.equal(0.5); - this.validate(spritesheet, done); - }; - }); - - it('should create instance with BaseTexture source scale', function (done) - { - const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require - const baseTexture = new BaseTexture.fromImage(data.meta.image, undefined, undefined, 1.5); - const spritesheet = new Spritesheet(baseTexture, data); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1.png'); - expect(spritesheet.resolution).to.equal(0.5); - - this.validate(spritesheet, done); - }); - - it('should create instance with filename resolution', function (done) - { - const uri = path.resolve(this.resources, 'building1@2x.json'); - const data = require(uri); // eslint-disable-line global-require - const image = new Image(); - - image.src = path.join(this.resources, data.meta.image); - image.onload = () => - { - const baseTexture = new BaseTexture(image, null, 1); - const spritesheet = new Spritesheet(baseTexture, data, uri); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1@2x.png'); - expect(spritesheet.resolution).to.equal(2); - - this.validate(spritesheet, done); - }; - }); -}); +require('./Spritesheet'); +require('./SpritesheetLoader'); diff --git a/packages/text-bitmap/package.json b/packages/text-bitmap/package.json index a8a5f95..4b47da6 100644 --- a/packages/text-bitmap/package.json +++ b/packages/text-bitmap/package.json @@ -23,7 +23,7 @@ "build": "rollup -cp", "watch": "rollup -cw", "postversion": "npm run build", - "test": "exit 0" + "test": "tester" }, "files": [ "lib" @@ -31,12 +31,15 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha", "@pixi/sprite": "^5.0.0-alpha", "@pixi/settings": "^5.0.0-alpha", "@pixi/math": "^5.0.0-alpha", "@pixi/display": "^5.0.0-alpha" }, "devDependencies": { + "@pixi/spritesheet": "^5.0.0-alpha", + "@internal/tester": "^5.0.0-alpha", "@internal/builder": "^5.0.0-alpha", "rollup": "^0.50.0" } diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/packages/loaders/src/spritesheetParser.js b/packages/loaders/src/spritesheetParser.js deleted file mode 100644 index f7fa206..0000000 --- a/packages/loaders/src/spritesheetParser.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Resource } from 'resource-loader'; -import url from 'url'; -import { Spritesheet } from '@pixi/spritesheet'; - -export default function () -{ - return function spritesheetParser(resource, next) - { - const imageResourceName = `${resource.name}_image`; - - // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists - if (!resource.data - || resource.type !== Resource.TYPE.JSON - || !resource.data.frames - || this.resources[imageResourceName] - ) - { - next(); - - return; - } - - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - const resourcePath = getResourcePath(resource, this.baseUrl); - - // load the image for this sheet - this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) - { - const spritesheet = new Spritesheet( - res.texture.baseTexture, - resource.data, - resource.url - ); - - spritesheet.parse(() => - { - resource.spritesheet = spritesheet; - resource.textures = spritesheet.textures; - next(); - }); - }); - }; -} - -export function getResourcePath(resource, baseUrl) -{ - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - return resource.data.meta.image; - } - - return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); -} diff --git a/packages/loaders/test/bitmapFontParser.js b/packages/loaders/test/bitmapFontParser.js deleted file mode 100644 index 43ef194..0000000 --- a/packages/loaders/test/bitmapFontParser.js +++ /dev/null @@ -1,325 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const { BitmapText } = require('@pixi/text-bitmap'); -const { BaseTextureCache, TextureCache } = require('@pixi/utils'); -const { Texture, BaseTexture } = require('@pixi/core'); -const { Spritesheet } = require('@pixi/spritesheet'); -const { bitmapFontParser, parseBitmapFontData } = require('../'); - -describe('PIXI.loaders.bitmapFontParser', function () -{ - afterEach(function () - { - for (const font in BitmapText.fonts) - { - delete BitmapText.fonts[font]; - } - for (const baseTexture in BaseTextureCache) - { - delete BaseTextureCache[baseTexture]; - } - for (const texture in TextureCache) - { - delete TextureCache[texture]; - } - }); - - before(function (done) - { - const resolveURL = (url) => path.resolve(this.resources, url); - - this.resources = path.join(__dirname, 'resources'); - this.fontXML = null; - this.fontScaledXML = null; - this.fontImage = null; - this.fontScaledImage = null; - this.atlasImage = null; - this.atlasScaledImage = null; - this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require - this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require - - const loadXML = (url) => new Promise((resolve) => - fs.readFile(resolveURL(url), 'utf8', (err, data) => - { - expect(err).to.be.null; - resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); - })); - - const loadImage = (url) => new Promise((resolve) => - { - const image = new Image(); - - image.onload = () => resolve(image); - image.src = resolveURL(url); - }); - - Promise.all([ - loadXML('font.fnt'), - loadXML('font@0.5x.fnt'), - loadImage('font.png'), - loadImage('font@0.5x.png'), - loadImage('atlas.png'), - loadImage('atlas@0.5x.png'), - ]).then(([ - fontXML, - fontScaledXML, - fontImage, - fontScaledImage, - atlasImage, - atlasScaledImage, - ]) => - { - this.fontXML = fontXML; - this.fontScaledXML = fontScaledXML; - this.fontImage = fontImage; - this.fontScaledImage = fontScaledImage; - this.atlasImage = atlasImage; - this.atlasScaledImage = atlasScaledImage; - done(); - }); - }); - - it('should exist and return a function', function () - { - expect(bitmapFontParser).to.be.a('function'); - expect(bitmapFontParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not XML', function () - { - const spy = sinon.spy(); - const res = {}; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is not properly formatted XML', function () - { - const spy = sinon.spy(); - const res = { data: document.createDocumentFragment() }; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - // TODO: Test the texture cache code path. - // TODO: Test the loading texture code path. - // TODO: Test data-url code paths. - - it('should properly register bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charA.texture.frame.x).to.equal(2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charB.texture.frame.x).to.equal(2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charC.texture.frame.x).to.equal(23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charD.texture.frame.x).to.equal(19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register SCALED bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); - const font = BitmapText.registerFont(this.fontScaledXML, 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register bitmap font NESTED into spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); - - it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); - - spritesheet.resolution = 1; - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); -}); - -describe('PIXI.loaders.parseBitmapFontData', function () -{ - it('should exist', function () - { - expect(parseBitmapFontData).to.be.a('function'); - }); - - // TODO: Test the parser code. -}); diff --git a/packages/loaders/test/index.js b/packages/loaders/test/index.js index f00de5f..72c6554 100644 --- a/packages/loaders/test/index.js +++ b/packages/loaders/test/index.js @@ -1,4 +1,2 @@ -require('./bitmapFontParser'); require('./loader'); -require('./spritesheetParser'); require('./textureParser'); diff --git a/packages/loaders/test/resources/atlas.json b/packages/loaders/test/resources/atlas.json deleted file mode 100644 index 86e65a5..0000000 --- a/packages/loaders/test/resources/atlas.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas.png b/packages/loaders/test/resources/atlas.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/atlas@0.5x.json b/packages/loaders/test/resources/atlas@0.5x.json deleted file mode 100644 index ae990a1..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas@0.5x.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas@0.5x.png b/packages/loaders/test/resources/atlas@0.5x.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font.fnt b/packages/loaders/test/resources/font.fnt deleted file mode 100644 index 56e1060..0000000 --- a/packages/loaders/test/resources/font.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font.png b/packages/loaders/test/resources/font.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font@0.5x.fnt b/packages/loaders/test/resources/font@0.5x.fnt deleted file mode 100644 index 6c247c7..0000000 --- a/packages/loaders/test/resources/font@0.5x.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font@0.5x.png b/packages/loaders/test/resources/font@0.5x.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/spritesheetParser.js b/packages/loaders/test/spritesheetParser.js deleted file mode 100644 index 98890b8..0000000 --- a/packages/loaders/test/spritesheetParser.js +++ /dev/null @@ -1,213 +0,0 @@ -const path = require('path'); -const { spritesheetParser, Resource, Loader, getResourcePath } = require('../'); -const { Texture, BaseTexture } = require('@pixi/core'); - -describe('PIXI.loaders.spritesheetParser', function () -{ - it('should exist and return a function', function () - { - expect(spritesheetParser).to.be.a('function'); - expect(spritesheetParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not JSON', function () - { - const spy = sinon.spy(); - const res = {}; - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is JSON, but improper format', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, {}); - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should load the image & create textures if json is properly formatted', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); - const loader = new Loader(); - const addStub = sinon.stub(loader, 'add'); - const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); - - imgRes.texture = new Texture(new BaseTexture(imgRes.data)); - - addStub.yields(imgRes); - - spritesheetParser().call(loader, res, spy); - - addStub.restore(); - - expect(spy).to.have.been.calledOnce; - expect(addStub).to.have.been.calledWith( - `${res.name}_image`, - `${path.dirname(res.url)}/${res.data.meta.image}` - ); - expect(res).to.have.property('textures') - .that.is.an('object') - .with.keys(Object.keys(getJsonSpritesheet().frames)) - .and.has.property('0.png') - .that.is.an.instanceof(Texture); - }); - - it('should build the image url', function () - { - function getPath(url, image) - { - return getResourcePath({ - url, - data: { meta: { image } }, - }); - } - - let result = getPath('http://some.com/spritesheet.json', 'img.png'); - - expect(result).to.be.equals('http://some.com/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('http://some.com/some/img.png'); - - result = getPath('/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/img.png'); - - result = getPath('/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('/some/img.png'); - }); - - // TODO: Test that rectangles are created correctly. - // TODO: Test that bathc processing works correctly. - // TODO: Test that resolution processing works correctly. - // TODO: Test that metadata is honored. -}); - -function createMockResource(type, data) -{ - const name = `${Math.floor(Date.now() * Math.random())}`; - - return { - url: `http://localhost/doesnt_exist/${name}`, - name, - type, - data, - metadata: {}, - }; -} - -function getJsonSpritesheet() -{ - /* eslint-disable */ - return {"frames": { - "0.png": - { - "frame": {"x":14,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "1.png": - { - "frame": {"x":14,"y":42,"w":12,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, - "sourceSize": {"w":12,"h":14} - }, - "2.png": - { - "frame": {"x":14,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "3.png": - { - "frame": {"x":42,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "4.png": - { - "frame": {"x":28,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "5.png": - { - "frame": {"x":14,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "6.png": - { - "frame": {"x":0,"y":42,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "7.png": - { - "frame": {"x":0,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "8.png": - { - "frame": {"x":0,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "9.png": - { - "frame": {"x":0,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }}, - "meta": { - "app": "http://www.texturepacker.com", - "version": "1.0", - "image": "hud.png", - "format": "RGBA8888", - "size": {"w":64,"h":64}, - "scale": "1", - "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" - } - }; - /* eslint-enable */ -} diff --git a/packages/mixin-app-loader/LICENSE b/packages/mixin-app-loader/LICENSE new file mode 100644 index 0000000..5d16b50 --- /dev/null +++ b/packages/mixin-app-loader/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2013-2017 Mathew Groves, Chad Engler + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/packages/mixin-app-loader/README.md b/packages/mixin-app-loader/README.md new file mode 100644 index 0000000..3e89f86 --- /dev/null +++ b/packages/mixin-app-loader/README.md @@ -0,0 +1,13 @@ +# @pixi/mixin-app-loader + +## Installation + +```bash +npm install @pixi/mixin-app-loader +``` + +## Usage + +```js +import '@pixi/mixin-app-loader'; +``` \ No newline at end of file diff --git a/packages/mixin-app-loader/package.json b/packages/mixin-app-loader/package.json new file mode 100644 index 0000000..869ed4e --- /dev/null +++ b/packages/mixin-app-loader/package.json @@ -0,0 +1,41 @@ +{ + "name": "@pixi/mixin-app-loader", + "version": "5.0.0-alpha", + "main": "lib/mixin-app-loader.js", + "module": "lib/mixin-app-loader.es.js", + "description": "Support for loader in Application", + "author": "Mat Groves", + "contributors": [ + "Matt Karl " + ], + "homepage": "http://pixijs.com/", + "bugs": "https://github.com/pixijs/pixi.js/issues", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/pixijs/pixi.js.git" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "build:dev": "rollup -c", + "build": "rollup -cp", + "watch": "rollup -cw", + "postversion": "npm run build", + "test": "tester" + }, + "files": [ + "lib" + ], + "dependencies": { + "@pixi/app": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" + }, + "devDependencies": { + "@pixi/utils": "^5.0.0-alpha", + "@internal/tester": "^5.0.0-alpha", + "@internal/builder": "^5.0.0-alpha", + "rollup": "^0.50.0" + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/rollup.config.js b/packages/mixin-app-loader/rollup.config.js new file mode 100644 index 0000000..a73c874 --- /dev/null +++ b/packages/mixin-app-loader/rollup.config.js @@ -0,0 +1 @@ +export { default } from '@internal/builder'; diff --git a/packages/mixin-app-loader/src/index.js b/packages/mixin-app-loader/src/index.js new file mode 100644 index 0000000..354997a --- /dev/null +++ b/packages/mixin-app-loader/src/index.js @@ -0,0 +1,38 @@ +import { Application } from '@pixi/app'; +import { Loader, shared } from '@pixi/loaders'; + +Application.prototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperties(Application.prototype, { + loader: { + get() + { + if (!this._loader && this._options) + { + const { sharedLoader } = this._options; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +Application.prototype._parentDestroy = Application.prototype.destroy; +Application.prototype.destroy = function destroy(removeView) +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(removeView); +}; diff --git a/packages/mixin-app-loader/test/.eslintrc.json b/packages/mixin-app-loader/test/.eslintrc.json new file mode 100644 index 0000000..2094b04 --- /dev/null +++ b/packages/mixin-app-loader/test/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "globals": { + "expect": false, + "assert": false, + "sinon": false, + "PIXI": false + }, + "rules": { + "func-names": 0, + "no-unused-expressions": 0 + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/test/index.js b/packages/mixin-app-loader/test/index.js new file mode 100644 index 0000000..3468fe5 --- /dev/null +++ b/packages/mixin-app-loader/test/index.js @@ -0,0 +1,35 @@ +const { Application } = require('@pixi/app'); +const { Loader, shared } = require('@pixi/loaders'); +const { skipHello } = require('@pixi/utils'); + +require('../'); + +skipHello(); + +describe('PIXI.Application#loader', function () +{ + it('should contain loader property', function () + { + const obj = new Application(); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); + + it('should use sharedLoader option', function () + { + const obj = new Application({ sharedLoader: true }); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + expect(obj.loader).to.equal(shared); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); +}); diff --git a/packages/spritesheet/package.json b/packages/spritesheet/package.json index 34749e2..5f35442 100644 --- a/packages/spritesheet/package.json +++ b/packages/spritesheet/package.json @@ -31,7 +31,8 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/math": "^5.0.0-alpha", - "@pixi/utils": "^5.0.0-alpha" + "@pixi/utils": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" }, "devDependencies": { "@internal/builder": "^5.0.0-alpha", diff --git a/packages/spritesheet/src/SpritesheetLoader.js b/packages/spritesheet/src/SpritesheetLoader.js new file mode 100644 index 0000000..c7b34ea --- /dev/null +++ b/packages/spritesheet/src/SpritesheetLoader.js @@ -0,0 +1,83 @@ +import url from 'url'; +import { Resource, Loader } from '@pixi/loaders'; +import Spritesheet from './Spritesheet'; + +/** + * {@link PIXI.loaders.Loader Loader} middleware for loading + * texture atlases that have been created with TexturePacker or + * similar JSON-based spritesheet. This automatically generates + * Texture resources. + * @class + * @memberof PIXI + */ +export default class SpritesheetLoader +{ + /** + * Middleware function to support Spritesheets, this is automatically installed. + * @see PIXI.loaders.Loader.useMiddleware + */ + static middleware() + { + return function spritesheetLoader(resource, next) + { + const imageResourceName = `${resource.name}_image`; + + // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists + if (!resource.data + || resource.type !== Resource.TYPE.JSON + || !resource.data.frames + || this.resources[imageResourceName] + ) + { + next(); + + return; + } + + const loadOptions = { + crossOrigin: resource.crossOrigin, + loadType: Resource.LOAD_TYPE.IMAGE, + metadata: resource.metadata.imageMetadata, + parentResource: resource, + }; + + const resourcePath = SpritesheetLoader.getResourcePath(resource, this.baseUrl); + + // load the image for this sheet + this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) + { + const spritesheet = new Spritesheet( + res.texture.baseTexture, + resource.data, + resource.url + ); + + spritesheet.parse(() => + { + resource.spritesheet = spritesheet; + resource.textures = spritesheet.textures; + next(); + }); + }); + }; + } + + /** + * Get the spritesheets root path + * @param {PIXI.loader.Resource} resource - Resource to check path + * @param {string} baseUrl - Base root url + */ + static getResourcePath(resource, baseUrl) + { + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); + } +} + +// Install loader support for Spritesheet objects +Loader.useMiddleware(SpritesheetLoader.middleware); diff --git a/packages/spritesheet/src/index.js b/packages/spritesheet/src/index.js index 51d96a1..b9ee1e8 100644 --- a/packages/spritesheet/src/index.js +++ b/packages/spritesheet/src/index.js @@ -1 +1,2 @@ export { default as Spritesheet } from './Spritesheet'; +export { default as SpritesheetLoader } from './SpritesheetLoader'; diff --git a/packages/spritesheet/test/Spritesheet.js b/packages/spritesheet/test/Spritesheet.js new file mode 100644 index 0000000..35d8688 --- /dev/null +++ b/packages/spritesheet/test/Spritesheet.js @@ -0,0 +1,105 @@ +const { Spritesheet } = require('../'); +const { BaseTexture, Texture } = require('@pixi/core'); +const path = require('path'); + +describe('PIXI.Spritesheet', function () +{ + before(function () + { + this.resources = path.join(__dirname, 'resources'); + this.validate = function (spritesheet, done) + { + spritesheet.parse(function (textures) + { + const id = 'goldmine_10_5.png'; + const width = Math.floor(spritesheet.data.frames[id].frame.w); + const height = Math.floor(spritesheet.data.frames[id].frame.h); + + expect(Object.keys(textures).length).to.equal(1); + expect(Object.keys(spritesheet.textures).length).to.equal(1); + expect(textures[id]).to.be.an.instanceof(Texture); + expect(textures[id].width).to.equal(width / spritesheet.resolution); + expect(textures[id].height).to.equal(height / spritesheet.resolution); + expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); + spritesheet.destroy(true); + expect(spritesheet.textures).to.be.null; + expect(spritesheet.baseTexture).to.be.null; + done(); + }); + }; + }); + + it('should exist on PIXI', function () + { + expect(Spritesheet).to.be.a.function; + expect(Spritesheet.BATCH_SIZE).to.be.a.number; + }); + + it('should create an instance', function () + { + const baseTexture = new BaseTexture(); + const data = { + frames: {}, + meta: {}, + }; + + const spritesheet = new Spritesheet(baseTexture, data); + + expect(spritesheet.data).to.equal(data); + expect(spritesheet.baseTexture).to.equal(baseTexture); + expect(spritesheet.resolution).to.equal(1); + + spritesheet.destroy(true); + }); + + it('should create instance with scale resolution', function (done) + { + const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require + const image = new Image(); + + image.src = path.join(this.resources, data.meta.image); + image.onload = () => + { + const baseTexture = new BaseTexture(image, null, 1); + const spritesheet = new Spritesheet(baseTexture, data); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1.png'); + expect(spritesheet.resolution).to.equal(0.5); + this.validate(spritesheet, done); + }; + }); + + it('should create instance with BaseTexture source scale', function (done) + { + const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require + const baseTexture = new BaseTexture.fromImage(data.meta.image, undefined, undefined, 1.5); + const spritesheet = new Spritesheet(baseTexture, data); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1.png'); + expect(spritesheet.resolution).to.equal(0.5); + + this.validate(spritesheet, done); + }); + + it('should create instance with filename resolution', function (done) + { + const uri = path.resolve(this.resources, 'building1@2x.json'); + const data = require(uri); // eslint-disable-line global-require + const image = new Image(); + + image.src = path.join(this.resources, data.meta.image); + image.onload = () => + { + const baseTexture = new BaseTexture(image, null, 1); + const spritesheet = new Spritesheet(baseTexture, data, uri); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1@2x.png'); + expect(spritesheet.resolution).to.equal(2); + + this.validate(spritesheet, done); + }; + }); +}); diff --git a/packages/spritesheet/test/SpritesheetLoader.js b/packages/spritesheet/test/SpritesheetLoader.js new file mode 100644 index 0000000..e4205ec --- /dev/null +++ b/packages/spritesheet/test/SpritesheetLoader.js @@ -0,0 +1,214 @@ +const path = require('path'); +const { Loader, Resource } = require('@pixi/loaders'); +const { Texture, BaseTexture } = require('@pixi/core'); +const { SpritesheetLoader } = require('../'); + +describe('PIXI.SpritesheetLoader', function () +{ + it('should exist and return a function', function () + { + expect(SpritesheetLoader.middleware).to.be.a('function'); + expect(SpritesheetLoader.middleware()).to.be.a('function'); + }); + + it('should do nothing if the resource is not JSON', function () + { + const spy = sinon.spy(); + const res = {}; + + SpritesheetLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should do nothing if the resource is JSON, but improper format', function () + { + const spy = sinon.spy(); + const res = createMockResource(Resource.TYPE.JSON, {}); + + SpritesheetLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should load the image & create textures if json is properly formatted', function () + { + const spy = sinon.spy(); + const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); + const loader = new Loader(); + const addStub = sinon.stub(loader, 'add'); + const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); + + imgRes.texture = new Texture(new BaseTexture(imgRes.data)); + + addStub.yields(imgRes); + + SpritesheetLoader.middleware().call(loader, res, spy); + + addStub.restore(); + + expect(spy).to.have.been.calledOnce; + expect(addStub).to.have.been.calledWith( + `${res.name}_image`, + `${path.dirname(res.url)}/${res.data.meta.image}` + ); + expect(res).to.have.property('textures') + .that.is.an('object') + .with.keys(Object.keys(getJsonSpritesheet().frames)) + .and.has.property('0.png') + .that.is.an.instanceof(Texture); + }); + + it('should build the image url', function () + { + function getPath(url, image) + { + return SpritesheetLoader.getResourcePath({ + url, + data: { meta: { image } }, + }); + } + + let result = getPath('http://some.com/spritesheet.json', 'img.png'); + + expect(result).to.be.equals('http://some.com/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); + expect(result).to.be.equals('http://some.com/some/dir/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); + expect(result).to.be.equals('http://some.com/some/dir/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); + expect(result).to.be.equals('http://some.com/some/img.png'); + + result = getPath('/spritesheet.json', 'img.png'); + expect(result).to.be.equals('/img.png'); + + result = getPath('/some/dir/spritesheet.json', 'img.png'); + expect(result).to.be.equals('/some/dir/img.png'); + + result = getPath('/some/dir/spritesheet.json', './img.png'); + expect(result).to.be.equals('/some/dir/img.png'); + + result = getPath('/some/dir/spritesheet.json', '../img.png'); + expect(result).to.be.equals('/some/img.png'); + }); + + // TODO: Test that rectangles are created correctly. + // TODO: Test that bathc processing works correctly. + // TODO: Test that resolution processing works correctly. + // TODO: Test that metadata is honored. +}); + +function createMockResource(type, data) +{ + const name = `${Math.floor(Date.now() * Math.random())}`; + + return { + url: `http://localhost/doesnt_exist/${name}`, + name, + type, + data, + metadata: {}, + }; +} + +function getJsonSpritesheet() +{ + /* eslint-disable */ + return {"frames": { + "0.png": + { + "frame": {"x":14,"y":28,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "1.png": + { + "frame": {"x":14,"y":42,"w":12,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, + "sourceSize": {"w":12,"h":14} + }, + "2.png": + { + "frame": {"x":14,"y":14,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "3.png": + { + "frame": {"x":42,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "4.png": + { + "frame": {"x":28,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "5.png": + { + "frame": {"x":14,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "6.png": + { + "frame": {"x":0,"y":42,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "7.png": + { + "frame": {"x":0,"y":28,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "8.png": + { + "frame": {"x":0,"y":14,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "9.png": + { + "frame": {"x":0,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }}, + "meta": { + "app": "http://www.texturepacker.com", + "version": "1.0", + "image": "hud.png", + "format": "RGBA8888", + "size": {"w":64,"h":64}, + "scale": "1", + "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" + } + }; + /* eslint-enable */ +} diff --git a/packages/spritesheet/test/index.js b/packages/spritesheet/test/index.js index 35d8688..8cf5636 100644 --- a/packages/spritesheet/test/index.js +++ b/packages/spritesheet/test/index.js @@ -1,105 +1,2 @@ -const { Spritesheet } = require('../'); -const { BaseTexture, Texture } = require('@pixi/core'); -const path = require('path'); - -describe('PIXI.Spritesheet', function () -{ - before(function () - { - this.resources = path.join(__dirname, 'resources'); - this.validate = function (spritesheet, done) - { - spritesheet.parse(function (textures) - { - const id = 'goldmine_10_5.png'; - const width = Math.floor(spritesheet.data.frames[id].frame.w); - const height = Math.floor(spritesheet.data.frames[id].frame.h); - - expect(Object.keys(textures).length).to.equal(1); - expect(Object.keys(spritesheet.textures).length).to.equal(1); - expect(textures[id]).to.be.an.instanceof(Texture); - expect(textures[id].width).to.equal(width / spritesheet.resolution); - expect(textures[id].height).to.equal(height / spritesheet.resolution); - expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); - spritesheet.destroy(true); - expect(spritesheet.textures).to.be.null; - expect(spritesheet.baseTexture).to.be.null; - done(); - }); - }; - }); - - it('should exist on PIXI', function () - { - expect(Spritesheet).to.be.a.function; - expect(Spritesheet.BATCH_SIZE).to.be.a.number; - }); - - it('should create an instance', function () - { - const baseTexture = new BaseTexture(); - const data = { - frames: {}, - meta: {}, - }; - - const spritesheet = new Spritesheet(baseTexture, data); - - expect(spritesheet.data).to.equal(data); - expect(spritesheet.baseTexture).to.equal(baseTexture); - expect(spritesheet.resolution).to.equal(1); - - spritesheet.destroy(true); - }); - - it('should create instance with scale resolution', function (done) - { - const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require - const image = new Image(); - - image.src = path.join(this.resources, data.meta.image); - image.onload = () => - { - const baseTexture = new BaseTexture(image, null, 1); - const spritesheet = new Spritesheet(baseTexture, data); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1.png'); - expect(spritesheet.resolution).to.equal(0.5); - this.validate(spritesheet, done); - }; - }); - - it('should create instance with BaseTexture source scale', function (done) - { - const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require - const baseTexture = new BaseTexture.fromImage(data.meta.image, undefined, undefined, 1.5); - const spritesheet = new Spritesheet(baseTexture, data); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1.png'); - expect(spritesheet.resolution).to.equal(0.5); - - this.validate(spritesheet, done); - }); - - it('should create instance with filename resolution', function (done) - { - const uri = path.resolve(this.resources, 'building1@2x.json'); - const data = require(uri); // eslint-disable-line global-require - const image = new Image(); - - image.src = path.join(this.resources, data.meta.image); - image.onload = () => - { - const baseTexture = new BaseTexture(image, null, 1); - const spritesheet = new Spritesheet(baseTexture, data, uri); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1@2x.png'); - expect(spritesheet.resolution).to.equal(2); - - this.validate(spritesheet, done); - }; - }); -}); +require('./Spritesheet'); +require('./SpritesheetLoader'); diff --git a/packages/text-bitmap/package.json b/packages/text-bitmap/package.json index a8a5f95..4b47da6 100644 --- a/packages/text-bitmap/package.json +++ b/packages/text-bitmap/package.json @@ -23,7 +23,7 @@ "build": "rollup -cp", "watch": "rollup -cw", "postversion": "npm run build", - "test": "exit 0" + "test": "tester" }, "files": [ "lib" @@ -31,12 +31,15 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha", "@pixi/sprite": "^5.0.0-alpha", "@pixi/settings": "^5.0.0-alpha", "@pixi/math": "^5.0.0-alpha", "@pixi/display": "^5.0.0-alpha" }, "devDependencies": { + "@pixi/spritesheet": "^5.0.0-alpha", + "@internal/tester": "^5.0.0-alpha", "@internal/builder": "^5.0.0-alpha", "rollup": "^0.50.0" } diff --git a/packages/text-bitmap/src/BitmapFontLoader.js b/packages/text-bitmap/src/BitmapFontLoader.js new file mode 100644 index 0000000..3956060 --- /dev/null +++ b/packages/text-bitmap/src/BitmapFontLoader.js @@ -0,0 +1,112 @@ +import * as path from 'path'; +import { TextureCache } from '@pixi/utils'; +import { Resource, Loader } from '@pixi/loaders'; +import BitmapText from './BitmapText'; + +/** + * {@link PIXI.loaders.Loader Loader} middleware for loading + * bitmap-based fonts suitable for using with {@link PIXI.BitmapText}. + * @class + * @memberof PIXI + */ +export default class BitmapFontLoader +{ + /** + * Register a BitmapText font from loader resource. + * + * @param {PIXI.loaders.Resource} resource - Loader resource. + * @param {PIXI.Texture} texture - Reference to texture. + */ + static parse(resource, texture) + { + resource.bitmapFont = BitmapText.registerFont(resource.data, texture); + } + + /** + * Middleware function to support BitmapText fonts, this is automatically installed. + * @see PIXI.loaders.Loader.useMiddleware + */ + static middleware() + { + return function bitmapFontLoader(resource, next) + { + // skip if no data or not xml data + if (!resource.data || resource.type !== Resource.TYPE.XML) + { + next(); + + return; + } + + // skip if not bitmap font data, using some silly duck-typing + if (resource.data.getElementsByTagName('page').length === 0 + || resource.data.getElementsByTagName('info').length === 0 + || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null + ) + { + next(); + + return; + } + + let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; + + if (resource.isDataUrl) + { + if (xmlUrl === '.') + { + xmlUrl = ''; + } + + if (this.baseUrl && xmlUrl) + { + // if baseurl has a trailing slash then add one to xmlUrl so the replace works below + if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') + { + xmlUrl += '/'; + } + } + } + + // remove baseUrl from xmlUrl + xmlUrl = xmlUrl.replace(this.baseUrl, ''); + + // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. + if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') + { + xmlUrl += '/'; + } + + const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); + + if (TextureCache[textureUrl]) + { + // reuse existing texture + BitmapFontLoader.parse(resource, TextureCache[textureUrl]); + next(); + } + else + { + const loadOptions = { + crossOrigin: resource.crossOrigin, + loadType: Resource.LOAD_TYPE.IMAGE, + metadata: resource.metadata.imageMetadata, + parentResource: resource, + }; + + // load the texture for the font + this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => + { + BitmapFontLoader.parse(resource, res.texture); + next(); + }); + } + }; + } +} + +// Add custom add support for loading fnt files as XML +Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); + +// Install loader support for BitmapText +Loader.useMiddleware(BitmapFontLoader.middleware); diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/packages/loaders/src/spritesheetParser.js b/packages/loaders/src/spritesheetParser.js deleted file mode 100644 index f7fa206..0000000 --- a/packages/loaders/src/spritesheetParser.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Resource } from 'resource-loader'; -import url from 'url'; -import { Spritesheet } from '@pixi/spritesheet'; - -export default function () -{ - return function spritesheetParser(resource, next) - { - const imageResourceName = `${resource.name}_image`; - - // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists - if (!resource.data - || resource.type !== Resource.TYPE.JSON - || !resource.data.frames - || this.resources[imageResourceName] - ) - { - next(); - - return; - } - - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - const resourcePath = getResourcePath(resource, this.baseUrl); - - // load the image for this sheet - this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) - { - const spritesheet = new Spritesheet( - res.texture.baseTexture, - resource.data, - resource.url - ); - - spritesheet.parse(() => - { - resource.spritesheet = spritesheet; - resource.textures = spritesheet.textures; - next(); - }); - }); - }; -} - -export function getResourcePath(resource, baseUrl) -{ - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - return resource.data.meta.image; - } - - return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); -} diff --git a/packages/loaders/test/bitmapFontParser.js b/packages/loaders/test/bitmapFontParser.js deleted file mode 100644 index 43ef194..0000000 --- a/packages/loaders/test/bitmapFontParser.js +++ /dev/null @@ -1,325 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const { BitmapText } = require('@pixi/text-bitmap'); -const { BaseTextureCache, TextureCache } = require('@pixi/utils'); -const { Texture, BaseTexture } = require('@pixi/core'); -const { Spritesheet } = require('@pixi/spritesheet'); -const { bitmapFontParser, parseBitmapFontData } = require('../'); - -describe('PIXI.loaders.bitmapFontParser', function () -{ - afterEach(function () - { - for (const font in BitmapText.fonts) - { - delete BitmapText.fonts[font]; - } - for (const baseTexture in BaseTextureCache) - { - delete BaseTextureCache[baseTexture]; - } - for (const texture in TextureCache) - { - delete TextureCache[texture]; - } - }); - - before(function (done) - { - const resolveURL = (url) => path.resolve(this.resources, url); - - this.resources = path.join(__dirname, 'resources'); - this.fontXML = null; - this.fontScaledXML = null; - this.fontImage = null; - this.fontScaledImage = null; - this.atlasImage = null; - this.atlasScaledImage = null; - this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require - this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require - - const loadXML = (url) => new Promise((resolve) => - fs.readFile(resolveURL(url), 'utf8', (err, data) => - { - expect(err).to.be.null; - resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); - })); - - const loadImage = (url) => new Promise((resolve) => - { - const image = new Image(); - - image.onload = () => resolve(image); - image.src = resolveURL(url); - }); - - Promise.all([ - loadXML('font.fnt'), - loadXML('font@0.5x.fnt'), - loadImage('font.png'), - loadImage('font@0.5x.png'), - loadImage('atlas.png'), - loadImage('atlas@0.5x.png'), - ]).then(([ - fontXML, - fontScaledXML, - fontImage, - fontScaledImage, - atlasImage, - atlasScaledImage, - ]) => - { - this.fontXML = fontXML; - this.fontScaledXML = fontScaledXML; - this.fontImage = fontImage; - this.fontScaledImage = fontScaledImage; - this.atlasImage = atlasImage; - this.atlasScaledImage = atlasScaledImage; - done(); - }); - }); - - it('should exist and return a function', function () - { - expect(bitmapFontParser).to.be.a('function'); - expect(bitmapFontParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not XML', function () - { - const spy = sinon.spy(); - const res = {}; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is not properly formatted XML', function () - { - const spy = sinon.spy(); - const res = { data: document.createDocumentFragment() }; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - // TODO: Test the texture cache code path. - // TODO: Test the loading texture code path. - // TODO: Test data-url code paths. - - it('should properly register bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charA.texture.frame.x).to.equal(2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charB.texture.frame.x).to.equal(2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charC.texture.frame.x).to.equal(23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charD.texture.frame.x).to.equal(19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register SCALED bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); - const font = BitmapText.registerFont(this.fontScaledXML, 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register bitmap font NESTED into spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); - - it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); - - spritesheet.resolution = 1; - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); -}); - -describe('PIXI.loaders.parseBitmapFontData', function () -{ - it('should exist', function () - { - expect(parseBitmapFontData).to.be.a('function'); - }); - - // TODO: Test the parser code. -}); diff --git a/packages/loaders/test/index.js b/packages/loaders/test/index.js index f00de5f..72c6554 100644 --- a/packages/loaders/test/index.js +++ b/packages/loaders/test/index.js @@ -1,4 +1,2 @@ -require('./bitmapFontParser'); require('./loader'); -require('./spritesheetParser'); require('./textureParser'); diff --git a/packages/loaders/test/resources/atlas.json b/packages/loaders/test/resources/atlas.json deleted file mode 100644 index 86e65a5..0000000 --- a/packages/loaders/test/resources/atlas.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas.png b/packages/loaders/test/resources/atlas.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/atlas@0.5x.json b/packages/loaders/test/resources/atlas@0.5x.json deleted file mode 100644 index ae990a1..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas@0.5x.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas@0.5x.png b/packages/loaders/test/resources/atlas@0.5x.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font.fnt b/packages/loaders/test/resources/font.fnt deleted file mode 100644 index 56e1060..0000000 --- a/packages/loaders/test/resources/font.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font.png b/packages/loaders/test/resources/font.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font@0.5x.fnt b/packages/loaders/test/resources/font@0.5x.fnt deleted file mode 100644 index 6c247c7..0000000 --- a/packages/loaders/test/resources/font@0.5x.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font@0.5x.png b/packages/loaders/test/resources/font@0.5x.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/spritesheetParser.js b/packages/loaders/test/spritesheetParser.js deleted file mode 100644 index 98890b8..0000000 --- a/packages/loaders/test/spritesheetParser.js +++ /dev/null @@ -1,213 +0,0 @@ -const path = require('path'); -const { spritesheetParser, Resource, Loader, getResourcePath } = require('../'); -const { Texture, BaseTexture } = require('@pixi/core'); - -describe('PIXI.loaders.spritesheetParser', function () -{ - it('should exist and return a function', function () - { - expect(spritesheetParser).to.be.a('function'); - expect(spritesheetParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not JSON', function () - { - const spy = sinon.spy(); - const res = {}; - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is JSON, but improper format', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, {}); - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should load the image & create textures if json is properly formatted', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); - const loader = new Loader(); - const addStub = sinon.stub(loader, 'add'); - const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); - - imgRes.texture = new Texture(new BaseTexture(imgRes.data)); - - addStub.yields(imgRes); - - spritesheetParser().call(loader, res, spy); - - addStub.restore(); - - expect(spy).to.have.been.calledOnce; - expect(addStub).to.have.been.calledWith( - `${res.name}_image`, - `${path.dirname(res.url)}/${res.data.meta.image}` - ); - expect(res).to.have.property('textures') - .that.is.an('object') - .with.keys(Object.keys(getJsonSpritesheet().frames)) - .and.has.property('0.png') - .that.is.an.instanceof(Texture); - }); - - it('should build the image url', function () - { - function getPath(url, image) - { - return getResourcePath({ - url, - data: { meta: { image } }, - }); - } - - let result = getPath('http://some.com/spritesheet.json', 'img.png'); - - expect(result).to.be.equals('http://some.com/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('http://some.com/some/img.png'); - - result = getPath('/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/img.png'); - - result = getPath('/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('/some/img.png'); - }); - - // TODO: Test that rectangles are created correctly. - // TODO: Test that bathc processing works correctly. - // TODO: Test that resolution processing works correctly. - // TODO: Test that metadata is honored. -}); - -function createMockResource(type, data) -{ - const name = `${Math.floor(Date.now() * Math.random())}`; - - return { - url: `http://localhost/doesnt_exist/${name}`, - name, - type, - data, - metadata: {}, - }; -} - -function getJsonSpritesheet() -{ - /* eslint-disable */ - return {"frames": { - "0.png": - { - "frame": {"x":14,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "1.png": - { - "frame": {"x":14,"y":42,"w":12,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, - "sourceSize": {"w":12,"h":14} - }, - "2.png": - { - "frame": {"x":14,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "3.png": - { - "frame": {"x":42,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "4.png": - { - "frame": {"x":28,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "5.png": - { - "frame": {"x":14,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "6.png": - { - "frame": {"x":0,"y":42,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "7.png": - { - "frame": {"x":0,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "8.png": - { - "frame": {"x":0,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "9.png": - { - "frame": {"x":0,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }}, - "meta": { - "app": "http://www.texturepacker.com", - "version": "1.0", - "image": "hud.png", - "format": "RGBA8888", - "size": {"w":64,"h":64}, - "scale": "1", - "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" - } - }; - /* eslint-enable */ -} diff --git a/packages/mixin-app-loader/LICENSE b/packages/mixin-app-loader/LICENSE new file mode 100644 index 0000000..5d16b50 --- /dev/null +++ b/packages/mixin-app-loader/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2013-2017 Mathew Groves, Chad Engler + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/packages/mixin-app-loader/README.md b/packages/mixin-app-loader/README.md new file mode 100644 index 0000000..3e89f86 --- /dev/null +++ b/packages/mixin-app-loader/README.md @@ -0,0 +1,13 @@ +# @pixi/mixin-app-loader + +## Installation + +```bash +npm install @pixi/mixin-app-loader +``` + +## Usage + +```js +import '@pixi/mixin-app-loader'; +``` \ No newline at end of file diff --git a/packages/mixin-app-loader/package.json b/packages/mixin-app-loader/package.json new file mode 100644 index 0000000..869ed4e --- /dev/null +++ b/packages/mixin-app-loader/package.json @@ -0,0 +1,41 @@ +{ + "name": "@pixi/mixin-app-loader", + "version": "5.0.0-alpha", + "main": "lib/mixin-app-loader.js", + "module": "lib/mixin-app-loader.es.js", + "description": "Support for loader in Application", + "author": "Mat Groves", + "contributors": [ + "Matt Karl " + ], + "homepage": "http://pixijs.com/", + "bugs": "https://github.com/pixijs/pixi.js/issues", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/pixijs/pixi.js.git" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "build:dev": "rollup -c", + "build": "rollup -cp", + "watch": "rollup -cw", + "postversion": "npm run build", + "test": "tester" + }, + "files": [ + "lib" + ], + "dependencies": { + "@pixi/app": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" + }, + "devDependencies": { + "@pixi/utils": "^5.0.0-alpha", + "@internal/tester": "^5.0.0-alpha", + "@internal/builder": "^5.0.0-alpha", + "rollup": "^0.50.0" + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/rollup.config.js b/packages/mixin-app-loader/rollup.config.js new file mode 100644 index 0000000..a73c874 --- /dev/null +++ b/packages/mixin-app-loader/rollup.config.js @@ -0,0 +1 @@ +export { default } from '@internal/builder'; diff --git a/packages/mixin-app-loader/src/index.js b/packages/mixin-app-loader/src/index.js new file mode 100644 index 0000000..354997a --- /dev/null +++ b/packages/mixin-app-loader/src/index.js @@ -0,0 +1,38 @@ +import { Application } from '@pixi/app'; +import { Loader, shared } from '@pixi/loaders'; + +Application.prototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperties(Application.prototype, { + loader: { + get() + { + if (!this._loader && this._options) + { + const { sharedLoader } = this._options; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +Application.prototype._parentDestroy = Application.prototype.destroy; +Application.prototype.destroy = function destroy(removeView) +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(removeView); +}; diff --git a/packages/mixin-app-loader/test/.eslintrc.json b/packages/mixin-app-loader/test/.eslintrc.json new file mode 100644 index 0000000..2094b04 --- /dev/null +++ b/packages/mixin-app-loader/test/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "globals": { + "expect": false, + "assert": false, + "sinon": false, + "PIXI": false + }, + "rules": { + "func-names": 0, + "no-unused-expressions": 0 + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/test/index.js b/packages/mixin-app-loader/test/index.js new file mode 100644 index 0000000..3468fe5 --- /dev/null +++ b/packages/mixin-app-loader/test/index.js @@ -0,0 +1,35 @@ +const { Application } = require('@pixi/app'); +const { Loader, shared } = require('@pixi/loaders'); +const { skipHello } = require('@pixi/utils'); + +require('../'); + +skipHello(); + +describe('PIXI.Application#loader', function () +{ + it('should contain loader property', function () + { + const obj = new Application(); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); + + it('should use sharedLoader option', function () + { + const obj = new Application({ sharedLoader: true }); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + expect(obj.loader).to.equal(shared); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); +}); diff --git a/packages/spritesheet/package.json b/packages/spritesheet/package.json index 34749e2..5f35442 100644 --- a/packages/spritesheet/package.json +++ b/packages/spritesheet/package.json @@ -31,7 +31,8 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/math": "^5.0.0-alpha", - "@pixi/utils": "^5.0.0-alpha" + "@pixi/utils": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" }, "devDependencies": { "@internal/builder": "^5.0.0-alpha", diff --git a/packages/spritesheet/src/SpritesheetLoader.js b/packages/spritesheet/src/SpritesheetLoader.js new file mode 100644 index 0000000..c7b34ea --- /dev/null +++ b/packages/spritesheet/src/SpritesheetLoader.js @@ -0,0 +1,83 @@ +import url from 'url'; +import { Resource, Loader } from '@pixi/loaders'; +import Spritesheet from './Spritesheet'; + +/** + * {@link PIXI.loaders.Loader Loader} middleware for loading + * texture atlases that have been created with TexturePacker or + * similar JSON-based spritesheet. This automatically generates + * Texture resources. + * @class + * @memberof PIXI + */ +export default class SpritesheetLoader +{ + /** + * Middleware function to support Spritesheets, this is automatically installed. + * @see PIXI.loaders.Loader.useMiddleware + */ + static middleware() + { + return function spritesheetLoader(resource, next) + { + const imageResourceName = `${resource.name}_image`; + + // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists + if (!resource.data + || resource.type !== Resource.TYPE.JSON + || !resource.data.frames + || this.resources[imageResourceName] + ) + { + next(); + + return; + } + + const loadOptions = { + crossOrigin: resource.crossOrigin, + loadType: Resource.LOAD_TYPE.IMAGE, + metadata: resource.metadata.imageMetadata, + parentResource: resource, + }; + + const resourcePath = SpritesheetLoader.getResourcePath(resource, this.baseUrl); + + // load the image for this sheet + this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) + { + const spritesheet = new Spritesheet( + res.texture.baseTexture, + resource.data, + resource.url + ); + + spritesheet.parse(() => + { + resource.spritesheet = spritesheet; + resource.textures = spritesheet.textures; + next(); + }); + }); + }; + } + + /** + * Get the spritesheets root path + * @param {PIXI.loader.Resource} resource - Resource to check path + * @param {string} baseUrl - Base root url + */ + static getResourcePath(resource, baseUrl) + { + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); + } +} + +// Install loader support for Spritesheet objects +Loader.useMiddleware(SpritesheetLoader.middleware); diff --git a/packages/spritesheet/src/index.js b/packages/spritesheet/src/index.js index 51d96a1..b9ee1e8 100644 --- a/packages/spritesheet/src/index.js +++ b/packages/spritesheet/src/index.js @@ -1 +1,2 @@ export { default as Spritesheet } from './Spritesheet'; +export { default as SpritesheetLoader } from './SpritesheetLoader'; diff --git a/packages/spritesheet/test/Spritesheet.js b/packages/spritesheet/test/Spritesheet.js new file mode 100644 index 0000000..35d8688 --- /dev/null +++ b/packages/spritesheet/test/Spritesheet.js @@ -0,0 +1,105 @@ +const { Spritesheet } = require('../'); +const { BaseTexture, Texture } = require('@pixi/core'); +const path = require('path'); + +describe('PIXI.Spritesheet', function () +{ + before(function () + { + this.resources = path.join(__dirname, 'resources'); + this.validate = function (spritesheet, done) + { + spritesheet.parse(function (textures) + { + const id = 'goldmine_10_5.png'; + const width = Math.floor(spritesheet.data.frames[id].frame.w); + const height = Math.floor(spritesheet.data.frames[id].frame.h); + + expect(Object.keys(textures).length).to.equal(1); + expect(Object.keys(spritesheet.textures).length).to.equal(1); + expect(textures[id]).to.be.an.instanceof(Texture); + expect(textures[id].width).to.equal(width / spritesheet.resolution); + expect(textures[id].height).to.equal(height / spritesheet.resolution); + expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); + spritesheet.destroy(true); + expect(spritesheet.textures).to.be.null; + expect(spritesheet.baseTexture).to.be.null; + done(); + }); + }; + }); + + it('should exist on PIXI', function () + { + expect(Spritesheet).to.be.a.function; + expect(Spritesheet.BATCH_SIZE).to.be.a.number; + }); + + it('should create an instance', function () + { + const baseTexture = new BaseTexture(); + const data = { + frames: {}, + meta: {}, + }; + + const spritesheet = new Spritesheet(baseTexture, data); + + expect(spritesheet.data).to.equal(data); + expect(spritesheet.baseTexture).to.equal(baseTexture); + expect(spritesheet.resolution).to.equal(1); + + spritesheet.destroy(true); + }); + + it('should create instance with scale resolution', function (done) + { + const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require + const image = new Image(); + + image.src = path.join(this.resources, data.meta.image); + image.onload = () => + { + const baseTexture = new BaseTexture(image, null, 1); + const spritesheet = new Spritesheet(baseTexture, data); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1.png'); + expect(spritesheet.resolution).to.equal(0.5); + this.validate(spritesheet, done); + }; + }); + + it('should create instance with BaseTexture source scale', function (done) + { + const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require + const baseTexture = new BaseTexture.fromImage(data.meta.image, undefined, undefined, 1.5); + const spritesheet = new Spritesheet(baseTexture, data); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1.png'); + expect(spritesheet.resolution).to.equal(0.5); + + this.validate(spritesheet, done); + }); + + it('should create instance with filename resolution', function (done) + { + const uri = path.resolve(this.resources, 'building1@2x.json'); + const data = require(uri); // eslint-disable-line global-require + const image = new Image(); + + image.src = path.join(this.resources, data.meta.image); + image.onload = () => + { + const baseTexture = new BaseTexture(image, null, 1); + const spritesheet = new Spritesheet(baseTexture, data, uri); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1@2x.png'); + expect(spritesheet.resolution).to.equal(2); + + this.validate(spritesheet, done); + }; + }); +}); diff --git a/packages/spritesheet/test/SpritesheetLoader.js b/packages/spritesheet/test/SpritesheetLoader.js new file mode 100644 index 0000000..e4205ec --- /dev/null +++ b/packages/spritesheet/test/SpritesheetLoader.js @@ -0,0 +1,214 @@ +const path = require('path'); +const { Loader, Resource } = require('@pixi/loaders'); +const { Texture, BaseTexture } = require('@pixi/core'); +const { SpritesheetLoader } = require('../'); + +describe('PIXI.SpritesheetLoader', function () +{ + it('should exist and return a function', function () + { + expect(SpritesheetLoader.middleware).to.be.a('function'); + expect(SpritesheetLoader.middleware()).to.be.a('function'); + }); + + it('should do nothing if the resource is not JSON', function () + { + const spy = sinon.spy(); + const res = {}; + + SpritesheetLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should do nothing if the resource is JSON, but improper format', function () + { + const spy = sinon.spy(); + const res = createMockResource(Resource.TYPE.JSON, {}); + + SpritesheetLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should load the image & create textures if json is properly formatted', function () + { + const spy = sinon.spy(); + const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); + const loader = new Loader(); + const addStub = sinon.stub(loader, 'add'); + const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); + + imgRes.texture = new Texture(new BaseTexture(imgRes.data)); + + addStub.yields(imgRes); + + SpritesheetLoader.middleware().call(loader, res, spy); + + addStub.restore(); + + expect(spy).to.have.been.calledOnce; + expect(addStub).to.have.been.calledWith( + `${res.name}_image`, + `${path.dirname(res.url)}/${res.data.meta.image}` + ); + expect(res).to.have.property('textures') + .that.is.an('object') + .with.keys(Object.keys(getJsonSpritesheet().frames)) + .and.has.property('0.png') + .that.is.an.instanceof(Texture); + }); + + it('should build the image url', function () + { + function getPath(url, image) + { + return SpritesheetLoader.getResourcePath({ + url, + data: { meta: { image } }, + }); + } + + let result = getPath('http://some.com/spritesheet.json', 'img.png'); + + expect(result).to.be.equals('http://some.com/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); + expect(result).to.be.equals('http://some.com/some/dir/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); + expect(result).to.be.equals('http://some.com/some/dir/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); + expect(result).to.be.equals('http://some.com/some/img.png'); + + result = getPath('/spritesheet.json', 'img.png'); + expect(result).to.be.equals('/img.png'); + + result = getPath('/some/dir/spritesheet.json', 'img.png'); + expect(result).to.be.equals('/some/dir/img.png'); + + result = getPath('/some/dir/spritesheet.json', './img.png'); + expect(result).to.be.equals('/some/dir/img.png'); + + result = getPath('/some/dir/spritesheet.json', '../img.png'); + expect(result).to.be.equals('/some/img.png'); + }); + + // TODO: Test that rectangles are created correctly. + // TODO: Test that bathc processing works correctly. + // TODO: Test that resolution processing works correctly. + // TODO: Test that metadata is honored. +}); + +function createMockResource(type, data) +{ + const name = `${Math.floor(Date.now() * Math.random())}`; + + return { + url: `http://localhost/doesnt_exist/${name}`, + name, + type, + data, + metadata: {}, + }; +} + +function getJsonSpritesheet() +{ + /* eslint-disable */ + return {"frames": { + "0.png": + { + "frame": {"x":14,"y":28,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "1.png": + { + "frame": {"x":14,"y":42,"w":12,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, + "sourceSize": {"w":12,"h":14} + }, + "2.png": + { + "frame": {"x":14,"y":14,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "3.png": + { + "frame": {"x":42,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "4.png": + { + "frame": {"x":28,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "5.png": + { + "frame": {"x":14,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "6.png": + { + "frame": {"x":0,"y":42,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "7.png": + { + "frame": {"x":0,"y":28,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "8.png": + { + "frame": {"x":0,"y":14,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "9.png": + { + "frame": {"x":0,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }}, + "meta": { + "app": "http://www.texturepacker.com", + "version": "1.0", + "image": "hud.png", + "format": "RGBA8888", + "size": {"w":64,"h":64}, + "scale": "1", + "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" + } + }; + /* eslint-enable */ +} diff --git a/packages/spritesheet/test/index.js b/packages/spritesheet/test/index.js index 35d8688..8cf5636 100644 --- a/packages/spritesheet/test/index.js +++ b/packages/spritesheet/test/index.js @@ -1,105 +1,2 @@ -const { Spritesheet } = require('../'); -const { BaseTexture, Texture } = require('@pixi/core'); -const path = require('path'); - -describe('PIXI.Spritesheet', function () -{ - before(function () - { - this.resources = path.join(__dirname, 'resources'); - this.validate = function (spritesheet, done) - { - spritesheet.parse(function (textures) - { - const id = 'goldmine_10_5.png'; - const width = Math.floor(spritesheet.data.frames[id].frame.w); - const height = Math.floor(spritesheet.data.frames[id].frame.h); - - expect(Object.keys(textures).length).to.equal(1); - expect(Object.keys(spritesheet.textures).length).to.equal(1); - expect(textures[id]).to.be.an.instanceof(Texture); - expect(textures[id].width).to.equal(width / spritesheet.resolution); - expect(textures[id].height).to.equal(height / spritesheet.resolution); - expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); - spritesheet.destroy(true); - expect(spritesheet.textures).to.be.null; - expect(spritesheet.baseTexture).to.be.null; - done(); - }); - }; - }); - - it('should exist on PIXI', function () - { - expect(Spritesheet).to.be.a.function; - expect(Spritesheet.BATCH_SIZE).to.be.a.number; - }); - - it('should create an instance', function () - { - const baseTexture = new BaseTexture(); - const data = { - frames: {}, - meta: {}, - }; - - const spritesheet = new Spritesheet(baseTexture, data); - - expect(spritesheet.data).to.equal(data); - expect(spritesheet.baseTexture).to.equal(baseTexture); - expect(spritesheet.resolution).to.equal(1); - - spritesheet.destroy(true); - }); - - it('should create instance with scale resolution', function (done) - { - const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require - const image = new Image(); - - image.src = path.join(this.resources, data.meta.image); - image.onload = () => - { - const baseTexture = new BaseTexture(image, null, 1); - const spritesheet = new Spritesheet(baseTexture, data); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1.png'); - expect(spritesheet.resolution).to.equal(0.5); - this.validate(spritesheet, done); - }; - }); - - it('should create instance with BaseTexture source scale', function (done) - { - const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require - const baseTexture = new BaseTexture.fromImage(data.meta.image, undefined, undefined, 1.5); - const spritesheet = new Spritesheet(baseTexture, data); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1.png'); - expect(spritesheet.resolution).to.equal(0.5); - - this.validate(spritesheet, done); - }); - - it('should create instance with filename resolution', function (done) - { - const uri = path.resolve(this.resources, 'building1@2x.json'); - const data = require(uri); // eslint-disable-line global-require - const image = new Image(); - - image.src = path.join(this.resources, data.meta.image); - image.onload = () => - { - const baseTexture = new BaseTexture(image, null, 1); - const spritesheet = new Spritesheet(baseTexture, data, uri); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1@2x.png'); - expect(spritesheet.resolution).to.equal(2); - - this.validate(spritesheet, done); - }; - }); -}); +require('./Spritesheet'); +require('./SpritesheetLoader'); diff --git a/packages/text-bitmap/package.json b/packages/text-bitmap/package.json index a8a5f95..4b47da6 100644 --- a/packages/text-bitmap/package.json +++ b/packages/text-bitmap/package.json @@ -23,7 +23,7 @@ "build": "rollup -cp", "watch": "rollup -cw", "postversion": "npm run build", - "test": "exit 0" + "test": "tester" }, "files": [ "lib" @@ -31,12 +31,15 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha", "@pixi/sprite": "^5.0.0-alpha", "@pixi/settings": "^5.0.0-alpha", "@pixi/math": "^5.0.0-alpha", "@pixi/display": "^5.0.0-alpha" }, "devDependencies": { + "@pixi/spritesheet": "^5.0.0-alpha", + "@internal/tester": "^5.0.0-alpha", "@internal/builder": "^5.0.0-alpha", "rollup": "^0.50.0" } diff --git a/packages/text-bitmap/src/BitmapFontLoader.js b/packages/text-bitmap/src/BitmapFontLoader.js new file mode 100644 index 0000000..3956060 --- /dev/null +++ b/packages/text-bitmap/src/BitmapFontLoader.js @@ -0,0 +1,112 @@ +import * as path from 'path'; +import { TextureCache } from '@pixi/utils'; +import { Resource, Loader } from '@pixi/loaders'; +import BitmapText from './BitmapText'; + +/** + * {@link PIXI.loaders.Loader Loader} middleware for loading + * bitmap-based fonts suitable for using with {@link PIXI.BitmapText}. + * @class + * @memberof PIXI + */ +export default class BitmapFontLoader +{ + /** + * Register a BitmapText font from loader resource. + * + * @param {PIXI.loaders.Resource} resource - Loader resource. + * @param {PIXI.Texture} texture - Reference to texture. + */ + static parse(resource, texture) + { + resource.bitmapFont = BitmapText.registerFont(resource.data, texture); + } + + /** + * Middleware function to support BitmapText fonts, this is automatically installed. + * @see PIXI.loaders.Loader.useMiddleware + */ + static middleware() + { + return function bitmapFontLoader(resource, next) + { + // skip if no data or not xml data + if (!resource.data || resource.type !== Resource.TYPE.XML) + { + next(); + + return; + } + + // skip if not bitmap font data, using some silly duck-typing + if (resource.data.getElementsByTagName('page').length === 0 + || resource.data.getElementsByTagName('info').length === 0 + || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null + ) + { + next(); + + return; + } + + let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; + + if (resource.isDataUrl) + { + if (xmlUrl === '.') + { + xmlUrl = ''; + } + + if (this.baseUrl && xmlUrl) + { + // if baseurl has a trailing slash then add one to xmlUrl so the replace works below + if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') + { + xmlUrl += '/'; + } + } + } + + // remove baseUrl from xmlUrl + xmlUrl = xmlUrl.replace(this.baseUrl, ''); + + // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. + if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') + { + xmlUrl += '/'; + } + + const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); + + if (TextureCache[textureUrl]) + { + // reuse existing texture + BitmapFontLoader.parse(resource, TextureCache[textureUrl]); + next(); + } + else + { + const loadOptions = { + crossOrigin: resource.crossOrigin, + loadType: Resource.LOAD_TYPE.IMAGE, + metadata: resource.metadata.imageMetadata, + parentResource: resource, + }; + + // load the texture for the font + this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => + { + BitmapFontLoader.parse(resource, res.texture); + next(); + }); + } + }; + } +} + +// Add custom add support for loading fnt files as XML +Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); + +// Install loader support for BitmapText +Loader.useMiddleware(BitmapFontLoader.middleware); diff --git a/packages/text-bitmap/src/index.js b/packages/text-bitmap/src/index.js index a98e7b4..e5afd2c 100644 --- a/packages/text-bitmap/src/index.js +++ b/packages/text-bitmap/src/index.js @@ -1 +1,2 @@ export { default as BitmapText } from './BitmapText'; +export { default as BitmapFontLoader } from './BitmapFontLoader'; diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/packages/loaders/src/spritesheetParser.js b/packages/loaders/src/spritesheetParser.js deleted file mode 100644 index f7fa206..0000000 --- a/packages/loaders/src/spritesheetParser.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Resource } from 'resource-loader'; -import url from 'url'; -import { Spritesheet } from '@pixi/spritesheet'; - -export default function () -{ - return function spritesheetParser(resource, next) - { - const imageResourceName = `${resource.name}_image`; - - // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists - if (!resource.data - || resource.type !== Resource.TYPE.JSON - || !resource.data.frames - || this.resources[imageResourceName] - ) - { - next(); - - return; - } - - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - const resourcePath = getResourcePath(resource, this.baseUrl); - - // load the image for this sheet - this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) - { - const spritesheet = new Spritesheet( - res.texture.baseTexture, - resource.data, - resource.url - ); - - spritesheet.parse(() => - { - resource.spritesheet = spritesheet; - resource.textures = spritesheet.textures; - next(); - }); - }); - }; -} - -export function getResourcePath(resource, baseUrl) -{ - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - return resource.data.meta.image; - } - - return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); -} diff --git a/packages/loaders/test/bitmapFontParser.js b/packages/loaders/test/bitmapFontParser.js deleted file mode 100644 index 43ef194..0000000 --- a/packages/loaders/test/bitmapFontParser.js +++ /dev/null @@ -1,325 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const { BitmapText } = require('@pixi/text-bitmap'); -const { BaseTextureCache, TextureCache } = require('@pixi/utils'); -const { Texture, BaseTexture } = require('@pixi/core'); -const { Spritesheet } = require('@pixi/spritesheet'); -const { bitmapFontParser, parseBitmapFontData } = require('../'); - -describe('PIXI.loaders.bitmapFontParser', function () -{ - afterEach(function () - { - for (const font in BitmapText.fonts) - { - delete BitmapText.fonts[font]; - } - for (const baseTexture in BaseTextureCache) - { - delete BaseTextureCache[baseTexture]; - } - for (const texture in TextureCache) - { - delete TextureCache[texture]; - } - }); - - before(function (done) - { - const resolveURL = (url) => path.resolve(this.resources, url); - - this.resources = path.join(__dirname, 'resources'); - this.fontXML = null; - this.fontScaledXML = null; - this.fontImage = null; - this.fontScaledImage = null; - this.atlasImage = null; - this.atlasScaledImage = null; - this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require - this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require - - const loadXML = (url) => new Promise((resolve) => - fs.readFile(resolveURL(url), 'utf8', (err, data) => - { - expect(err).to.be.null; - resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); - })); - - const loadImage = (url) => new Promise((resolve) => - { - const image = new Image(); - - image.onload = () => resolve(image); - image.src = resolveURL(url); - }); - - Promise.all([ - loadXML('font.fnt'), - loadXML('font@0.5x.fnt'), - loadImage('font.png'), - loadImage('font@0.5x.png'), - loadImage('atlas.png'), - loadImage('atlas@0.5x.png'), - ]).then(([ - fontXML, - fontScaledXML, - fontImage, - fontScaledImage, - atlasImage, - atlasScaledImage, - ]) => - { - this.fontXML = fontXML; - this.fontScaledXML = fontScaledXML; - this.fontImage = fontImage; - this.fontScaledImage = fontScaledImage; - this.atlasImage = atlasImage; - this.atlasScaledImage = atlasScaledImage; - done(); - }); - }); - - it('should exist and return a function', function () - { - expect(bitmapFontParser).to.be.a('function'); - expect(bitmapFontParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not XML', function () - { - const spy = sinon.spy(); - const res = {}; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is not properly formatted XML', function () - { - const spy = sinon.spy(); - const res = { data: document.createDocumentFragment() }; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - // TODO: Test the texture cache code path. - // TODO: Test the loading texture code path. - // TODO: Test data-url code paths. - - it('should properly register bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charA.texture.frame.x).to.equal(2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charB.texture.frame.x).to.equal(2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charC.texture.frame.x).to.equal(23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charD.texture.frame.x).to.equal(19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register SCALED bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); - const font = BitmapText.registerFont(this.fontScaledXML, 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register bitmap font NESTED into spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); - - it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); - - spritesheet.resolution = 1; - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); -}); - -describe('PIXI.loaders.parseBitmapFontData', function () -{ - it('should exist', function () - { - expect(parseBitmapFontData).to.be.a('function'); - }); - - // TODO: Test the parser code. -}); diff --git a/packages/loaders/test/index.js b/packages/loaders/test/index.js index f00de5f..72c6554 100644 --- a/packages/loaders/test/index.js +++ b/packages/loaders/test/index.js @@ -1,4 +1,2 @@ -require('./bitmapFontParser'); require('./loader'); -require('./spritesheetParser'); require('./textureParser'); diff --git a/packages/loaders/test/resources/atlas.json b/packages/loaders/test/resources/atlas.json deleted file mode 100644 index 86e65a5..0000000 --- a/packages/loaders/test/resources/atlas.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas.png b/packages/loaders/test/resources/atlas.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/atlas@0.5x.json b/packages/loaders/test/resources/atlas@0.5x.json deleted file mode 100644 index ae990a1..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas@0.5x.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas@0.5x.png b/packages/loaders/test/resources/atlas@0.5x.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font.fnt b/packages/loaders/test/resources/font.fnt deleted file mode 100644 index 56e1060..0000000 --- a/packages/loaders/test/resources/font.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font.png b/packages/loaders/test/resources/font.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font@0.5x.fnt b/packages/loaders/test/resources/font@0.5x.fnt deleted file mode 100644 index 6c247c7..0000000 --- a/packages/loaders/test/resources/font@0.5x.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font@0.5x.png b/packages/loaders/test/resources/font@0.5x.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/spritesheetParser.js b/packages/loaders/test/spritesheetParser.js deleted file mode 100644 index 98890b8..0000000 --- a/packages/loaders/test/spritesheetParser.js +++ /dev/null @@ -1,213 +0,0 @@ -const path = require('path'); -const { spritesheetParser, Resource, Loader, getResourcePath } = require('../'); -const { Texture, BaseTexture } = require('@pixi/core'); - -describe('PIXI.loaders.spritesheetParser', function () -{ - it('should exist and return a function', function () - { - expect(spritesheetParser).to.be.a('function'); - expect(spritesheetParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not JSON', function () - { - const spy = sinon.spy(); - const res = {}; - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is JSON, but improper format', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, {}); - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should load the image & create textures if json is properly formatted', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); - const loader = new Loader(); - const addStub = sinon.stub(loader, 'add'); - const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); - - imgRes.texture = new Texture(new BaseTexture(imgRes.data)); - - addStub.yields(imgRes); - - spritesheetParser().call(loader, res, spy); - - addStub.restore(); - - expect(spy).to.have.been.calledOnce; - expect(addStub).to.have.been.calledWith( - `${res.name}_image`, - `${path.dirname(res.url)}/${res.data.meta.image}` - ); - expect(res).to.have.property('textures') - .that.is.an('object') - .with.keys(Object.keys(getJsonSpritesheet().frames)) - .and.has.property('0.png') - .that.is.an.instanceof(Texture); - }); - - it('should build the image url', function () - { - function getPath(url, image) - { - return getResourcePath({ - url, - data: { meta: { image } }, - }); - } - - let result = getPath('http://some.com/spritesheet.json', 'img.png'); - - expect(result).to.be.equals('http://some.com/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('http://some.com/some/img.png'); - - result = getPath('/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/img.png'); - - result = getPath('/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('/some/img.png'); - }); - - // TODO: Test that rectangles are created correctly. - // TODO: Test that bathc processing works correctly. - // TODO: Test that resolution processing works correctly. - // TODO: Test that metadata is honored. -}); - -function createMockResource(type, data) -{ - const name = `${Math.floor(Date.now() * Math.random())}`; - - return { - url: `http://localhost/doesnt_exist/${name}`, - name, - type, - data, - metadata: {}, - }; -} - -function getJsonSpritesheet() -{ - /* eslint-disable */ - return {"frames": { - "0.png": - { - "frame": {"x":14,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "1.png": - { - "frame": {"x":14,"y":42,"w":12,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, - "sourceSize": {"w":12,"h":14} - }, - "2.png": - { - "frame": {"x":14,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "3.png": - { - "frame": {"x":42,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "4.png": - { - "frame": {"x":28,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "5.png": - { - "frame": {"x":14,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "6.png": - { - "frame": {"x":0,"y":42,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "7.png": - { - "frame": {"x":0,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "8.png": - { - "frame": {"x":0,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "9.png": - { - "frame": {"x":0,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }}, - "meta": { - "app": "http://www.texturepacker.com", - "version": "1.0", - "image": "hud.png", - "format": "RGBA8888", - "size": {"w":64,"h":64}, - "scale": "1", - "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" - } - }; - /* eslint-enable */ -} diff --git a/packages/mixin-app-loader/LICENSE b/packages/mixin-app-loader/LICENSE new file mode 100644 index 0000000..5d16b50 --- /dev/null +++ b/packages/mixin-app-loader/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2013-2017 Mathew Groves, Chad Engler + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/packages/mixin-app-loader/README.md b/packages/mixin-app-loader/README.md new file mode 100644 index 0000000..3e89f86 --- /dev/null +++ b/packages/mixin-app-loader/README.md @@ -0,0 +1,13 @@ +# @pixi/mixin-app-loader + +## Installation + +```bash +npm install @pixi/mixin-app-loader +``` + +## Usage + +```js +import '@pixi/mixin-app-loader'; +``` \ No newline at end of file diff --git a/packages/mixin-app-loader/package.json b/packages/mixin-app-loader/package.json new file mode 100644 index 0000000..869ed4e --- /dev/null +++ b/packages/mixin-app-loader/package.json @@ -0,0 +1,41 @@ +{ + "name": "@pixi/mixin-app-loader", + "version": "5.0.0-alpha", + "main": "lib/mixin-app-loader.js", + "module": "lib/mixin-app-loader.es.js", + "description": "Support for loader in Application", + "author": "Mat Groves", + "contributors": [ + "Matt Karl " + ], + "homepage": "http://pixijs.com/", + "bugs": "https://github.com/pixijs/pixi.js/issues", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/pixijs/pixi.js.git" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "build:dev": "rollup -c", + "build": "rollup -cp", + "watch": "rollup -cw", + "postversion": "npm run build", + "test": "tester" + }, + "files": [ + "lib" + ], + "dependencies": { + "@pixi/app": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" + }, + "devDependencies": { + "@pixi/utils": "^5.0.0-alpha", + "@internal/tester": "^5.0.0-alpha", + "@internal/builder": "^5.0.0-alpha", + "rollup": "^0.50.0" + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/rollup.config.js b/packages/mixin-app-loader/rollup.config.js new file mode 100644 index 0000000..a73c874 --- /dev/null +++ b/packages/mixin-app-loader/rollup.config.js @@ -0,0 +1 @@ +export { default } from '@internal/builder'; diff --git a/packages/mixin-app-loader/src/index.js b/packages/mixin-app-loader/src/index.js new file mode 100644 index 0000000..354997a --- /dev/null +++ b/packages/mixin-app-loader/src/index.js @@ -0,0 +1,38 @@ +import { Application } from '@pixi/app'; +import { Loader, shared } from '@pixi/loaders'; + +Application.prototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperties(Application.prototype, { + loader: { + get() + { + if (!this._loader && this._options) + { + const { sharedLoader } = this._options; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +Application.prototype._parentDestroy = Application.prototype.destroy; +Application.prototype.destroy = function destroy(removeView) +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(removeView); +}; diff --git a/packages/mixin-app-loader/test/.eslintrc.json b/packages/mixin-app-loader/test/.eslintrc.json new file mode 100644 index 0000000..2094b04 --- /dev/null +++ b/packages/mixin-app-loader/test/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "globals": { + "expect": false, + "assert": false, + "sinon": false, + "PIXI": false + }, + "rules": { + "func-names": 0, + "no-unused-expressions": 0 + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/test/index.js b/packages/mixin-app-loader/test/index.js new file mode 100644 index 0000000..3468fe5 --- /dev/null +++ b/packages/mixin-app-loader/test/index.js @@ -0,0 +1,35 @@ +const { Application } = require('@pixi/app'); +const { Loader, shared } = require('@pixi/loaders'); +const { skipHello } = require('@pixi/utils'); + +require('../'); + +skipHello(); + +describe('PIXI.Application#loader', function () +{ + it('should contain loader property', function () + { + const obj = new Application(); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); + + it('should use sharedLoader option', function () + { + const obj = new Application({ sharedLoader: true }); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + expect(obj.loader).to.equal(shared); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); +}); diff --git a/packages/spritesheet/package.json b/packages/spritesheet/package.json index 34749e2..5f35442 100644 --- a/packages/spritesheet/package.json +++ b/packages/spritesheet/package.json @@ -31,7 +31,8 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/math": "^5.0.0-alpha", - "@pixi/utils": "^5.0.0-alpha" + "@pixi/utils": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" }, "devDependencies": { "@internal/builder": "^5.0.0-alpha", diff --git a/packages/spritesheet/src/SpritesheetLoader.js b/packages/spritesheet/src/SpritesheetLoader.js new file mode 100644 index 0000000..c7b34ea --- /dev/null +++ b/packages/spritesheet/src/SpritesheetLoader.js @@ -0,0 +1,83 @@ +import url from 'url'; +import { Resource, Loader } from '@pixi/loaders'; +import Spritesheet from './Spritesheet'; + +/** + * {@link PIXI.loaders.Loader Loader} middleware for loading + * texture atlases that have been created with TexturePacker or + * similar JSON-based spritesheet. This automatically generates + * Texture resources. + * @class + * @memberof PIXI + */ +export default class SpritesheetLoader +{ + /** + * Middleware function to support Spritesheets, this is automatically installed. + * @see PIXI.loaders.Loader.useMiddleware + */ + static middleware() + { + return function spritesheetLoader(resource, next) + { + const imageResourceName = `${resource.name}_image`; + + // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists + if (!resource.data + || resource.type !== Resource.TYPE.JSON + || !resource.data.frames + || this.resources[imageResourceName] + ) + { + next(); + + return; + } + + const loadOptions = { + crossOrigin: resource.crossOrigin, + loadType: Resource.LOAD_TYPE.IMAGE, + metadata: resource.metadata.imageMetadata, + parentResource: resource, + }; + + const resourcePath = SpritesheetLoader.getResourcePath(resource, this.baseUrl); + + // load the image for this sheet + this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) + { + const spritesheet = new Spritesheet( + res.texture.baseTexture, + resource.data, + resource.url + ); + + spritesheet.parse(() => + { + resource.spritesheet = spritesheet; + resource.textures = spritesheet.textures; + next(); + }); + }); + }; + } + + /** + * Get the spritesheets root path + * @param {PIXI.loader.Resource} resource - Resource to check path + * @param {string} baseUrl - Base root url + */ + static getResourcePath(resource, baseUrl) + { + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); + } +} + +// Install loader support for Spritesheet objects +Loader.useMiddleware(SpritesheetLoader.middleware); diff --git a/packages/spritesheet/src/index.js b/packages/spritesheet/src/index.js index 51d96a1..b9ee1e8 100644 --- a/packages/spritesheet/src/index.js +++ b/packages/spritesheet/src/index.js @@ -1 +1,2 @@ export { default as Spritesheet } from './Spritesheet'; +export { default as SpritesheetLoader } from './SpritesheetLoader'; diff --git a/packages/spritesheet/test/Spritesheet.js b/packages/spritesheet/test/Spritesheet.js new file mode 100644 index 0000000..35d8688 --- /dev/null +++ b/packages/spritesheet/test/Spritesheet.js @@ -0,0 +1,105 @@ +const { Spritesheet } = require('../'); +const { BaseTexture, Texture } = require('@pixi/core'); +const path = require('path'); + +describe('PIXI.Spritesheet', function () +{ + before(function () + { + this.resources = path.join(__dirname, 'resources'); + this.validate = function (spritesheet, done) + { + spritesheet.parse(function (textures) + { + const id = 'goldmine_10_5.png'; + const width = Math.floor(spritesheet.data.frames[id].frame.w); + const height = Math.floor(spritesheet.data.frames[id].frame.h); + + expect(Object.keys(textures).length).to.equal(1); + expect(Object.keys(spritesheet.textures).length).to.equal(1); + expect(textures[id]).to.be.an.instanceof(Texture); + expect(textures[id].width).to.equal(width / spritesheet.resolution); + expect(textures[id].height).to.equal(height / spritesheet.resolution); + expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); + spritesheet.destroy(true); + expect(spritesheet.textures).to.be.null; + expect(spritesheet.baseTexture).to.be.null; + done(); + }); + }; + }); + + it('should exist on PIXI', function () + { + expect(Spritesheet).to.be.a.function; + expect(Spritesheet.BATCH_SIZE).to.be.a.number; + }); + + it('should create an instance', function () + { + const baseTexture = new BaseTexture(); + const data = { + frames: {}, + meta: {}, + }; + + const spritesheet = new Spritesheet(baseTexture, data); + + expect(spritesheet.data).to.equal(data); + expect(spritesheet.baseTexture).to.equal(baseTexture); + expect(spritesheet.resolution).to.equal(1); + + spritesheet.destroy(true); + }); + + it('should create instance with scale resolution', function (done) + { + const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require + const image = new Image(); + + image.src = path.join(this.resources, data.meta.image); + image.onload = () => + { + const baseTexture = new BaseTexture(image, null, 1); + const spritesheet = new Spritesheet(baseTexture, data); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1.png'); + expect(spritesheet.resolution).to.equal(0.5); + this.validate(spritesheet, done); + }; + }); + + it('should create instance with BaseTexture source scale', function (done) + { + const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require + const baseTexture = new BaseTexture.fromImage(data.meta.image, undefined, undefined, 1.5); + const spritesheet = new Spritesheet(baseTexture, data); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1.png'); + expect(spritesheet.resolution).to.equal(0.5); + + this.validate(spritesheet, done); + }); + + it('should create instance with filename resolution', function (done) + { + const uri = path.resolve(this.resources, 'building1@2x.json'); + const data = require(uri); // eslint-disable-line global-require + const image = new Image(); + + image.src = path.join(this.resources, data.meta.image); + image.onload = () => + { + const baseTexture = new BaseTexture(image, null, 1); + const spritesheet = new Spritesheet(baseTexture, data, uri); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1@2x.png'); + expect(spritesheet.resolution).to.equal(2); + + this.validate(spritesheet, done); + }; + }); +}); diff --git a/packages/spritesheet/test/SpritesheetLoader.js b/packages/spritesheet/test/SpritesheetLoader.js new file mode 100644 index 0000000..e4205ec --- /dev/null +++ b/packages/spritesheet/test/SpritesheetLoader.js @@ -0,0 +1,214 @@ +const path = require('path'); +const { Loader, Resource } = require('@pixi/loaders'); +const { Texture, BaseTexture } = require('@pixi/core'); +const { SpritesheetLoader } = require('../'); + +describe('PIXI.SpritesheetLoader', function () +{ + it('should exist and return a function', function () + { + expect(SpritesheetLoader.middleware).to.be.a('function'); + expect(SpritesheetLoader.middleware()).to.be.a('function'); + }); + + it('should do nothing if the resource is not JSON', function () + { + const spy = sinon.spy(); + const res = {}; + + SpritesheetLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should do nothing if the resource is JSON, but improper format', function () + { + const spy = sinon.spy(); + const res = createMockResource(Resource.TYPE.JSON, {}); + + SpritesheetLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should load the image & create textures if json is properly formatted', function () + { + const spy = sinon.spy(); + const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); + const loader = new Loader(); + const addStub = sinon.stub(loader, 'add'); + const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); + + imgRes.texture = new Texture(new BaseTexture(imgRes.data)); + + addStub.yields(imgRes); + + SpritesheetLoader.middleware().call(loader, res, spy); + + addStub.restore(); + + expect(spy).to.have.been.calledOnce; + expect(addStub).to.have.been.calledWith( + `${res.name}_image`, + `${path.dirname(res.url)}/${res.data.meta.image}` + ); + expect(res).to.have.property('textures') + .that.is.an('object') + .with.keys(Object.keys(getJsonSpritesheet().frames)) + .and.has.property('0.png') + .that.is.an.instanceof(Texture); + }); + + it('should build the image url', function () + { + function getPath(url, image) + { + return SpritesheetLoader.getResourcePath({ + url, + data: { meta: { image } }, + }); + } + + let result = getPath('http://some.com/spritesheet.json', 'img.png'); + + expect(result).to.be.equals('http://some.com/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); + expect(result).to.be.equals('http://some.com/some/dir/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); + expect(result).to.be.equals('http://some.com/some/dir/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); + expect(result).to.be.equals('http://some.com/some/img.png'); + + result = getPath('/spritesheet.json', 'img.png'); + expect(result).to.be.equals('/img.png'); + + result = getPath('/some/dir/spritesheet.json', 'img.png'); + expect(result).to.be.equals('/some/dir/img.png'); + + result = getPath('/some/dir/spritesheet.json', './img.png'); + expect(result).to.be.equals('/some/dir/img.png'); + + result = getPath('/some/dir/spritesheet.json', '../img.png'); + expect(result).to.be.equals('/some/img.png'); + }); + + // TODO: Test that rectangles are created correctly. + // TODO: Test that bathc processing works correctly. + // TODO: Test that resolution processing works correctly. + // TODO: Test that metadata is honored. +}); + +function createMockResource(type, data) +{ + const name = `${Math.floor(Date.now() * Math.random())}`; + + return { + url: `http://localhost/doesnt_exist/${name}`, + name, + type, + data, + metadata: {}, + }; +} + +function getJsonSpritesheet() +{ + /* eslint-disable */ + return {"frames": { + "0.png": + { + "frame": {"x":14,"y":28,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "1.png": + { + "frame": {"x":14,"y":42,"w":12,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, + "sourceSize": {"w":12,"h":14} + }, + "2.png": + { + "frame": {"x":14,"y":14,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "3.png": + { + "frame": {"x":42,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "4.png": + { + "frame": {"x":28,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "5.png": + { + "frame": {"x":14,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "6.png": + { + "frame": {"x":0,"y":42,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "7.png": + { + "frame": {"x":0,"y":28,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "8.png": + { + "frame": {"x":0,"y":14,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "9.png": + { + "frame": {"x":0,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }}, + "meta": { + "app": "http://www.texturepacker.com", + "version": "1.0", + "image": "hud.png", + "format": "RGBA8888", + "size": {"w":64,"h":64}, + "scale": "1", + "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" + } + }; + /* eslint-enable */ +} diff --git a/packages/spritesheet/test/index.js b/packages/spritesheet/test/index.js index 35d8688..8cf5636 100644 --- a/packages/spritesheet/test/index.js +++ b/packages/spritesheet/test/index.js @@ -1,105 +1,2 @@ -const { Spritesheet } = require('../'); -const { BaseTexture, Texture } = require('@pixi/core'); -const path = require('path'); - -describe('PIXI.Spritesheet', function () -{ - before(function () - { - this.resources = path.join(__dirname, 'resources'); - this.validate = function (spritesheet, done) - { - spritesheet.parse(function (textures) - { - const id = 'goldmine_10_5.png'; - const width = Math.floor(spritesheet.data.frames[id].frame.w); - const height = Math.floor(spritesheet.data.frames[id].frame.h); - - expect(Object.keys(textures).length).to.equal(1); - expect(Object.keys(spritesheet.textures).length).to.equal(1); - expect(textures[id]).to.be.an.instanceof(Texture); - expect(textures[id].width).to.equal(width / spritesheet.resolution); - expect(textures[id].height).to.equal(height / spritesheet.resolution); - expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); - spritesheet.destroy(true); - expect(spritesheet.textures).to.be.null; - expect(spritesheet.baseTexture).to.be.null; - done(); - }); - }; - }); - - it('should exist on PIXI', function () - { - expect(Spritesheet).to.be.a.function; - expect(Spritesheet.BATCH_SIZE).to.be.a.number; - }); - - it('should create an instance', function () - { - const baseTexture = new BaseTexture(); - const data = { - frames: {}, - meta: {}, - }; - - const spritesheet = new Spritesheet(baseTexture, data); - - expect(spritesheet.data).to.equal(data); - expect(spritesheet.baseTexture).to.equal(baseTexture); - expect(spritesheet.resolution).to.equal(1); - - spritesheet.destroy(true); - }); - - it('should create instance with scale resolution', function (done) - { - const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require - const image = new Image(); - - image.src = path.join(this.resources, data.meta.image); - image.onload = () => - { - const baseTexture = new BaseTexture(image, null, 1); - const spritesheet = new Spritesheet(baseTexture, data); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1.png'); - expect(spritesheet.resolution).to.equal(0.5); - this.validate(spritesheet, done); - }; - }); - - it('should create instance with BaseTexture source scale', function (done) - { - const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require - const baseTexture = new BaseTexture.fromImage(data.meta.image, undefined, undefined, 1.5); - const spritesheet = new Spritesheet(baseTexture, data); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1.png'); - expect(spritesheet.resolution).to.equal(0.5); - - this.validate(spritesheet, done); - }); - - it('should create instance with filename resolution', function (done) - { - const uri = path.resolve(this.resources, 'building1@2x.json'); - const data = require(uri); // eslint-disable-line global-require - const image = new Image(); - - image.src = path.join(this.resources, data.meta.image); - image.onload = () => - { - const baseTexture = new BaseTexture(image, null, 1); - const spritesheet = new Spritesheet(baseTexture, data, uri); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1@2x.png'); - expect(spritesheet.resolution).to.equal(2); - - this.validate(spritesheet, done); - }; - }); -}); +require('./Spritesheet'); +require('./SpritesheetLoader'); diff --git a/packages/text-bitmap/package.json b/packages/text-bitmap/package.json index a8a5f95..4b47da6 100644 --- a/packages/text-bitmap/package.json +++ b/packages/text-bitmap/package.json @@ -23,7 +23,7 @@ "build": "rollup -cp", "watch": "rollup -cw", "postversion": "npm run build", - "test": "exit 0" + "test": "tester" }, "files": [ "lib" @@ -31,12 +31,15 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha", "@pixi/sprite": "^5.0.0-alpha", "@pixi/settings": "^5.0.0-alpha", "@pixi/math": "^5.0.0-alpha", "@pixi/display": "^5.0.0-alpha" }, "devDependencies": { + "@pixi/spritesheet": "^5.0.0-alpha", + "@internal/tester": "^5.0.0-alpha", "@internal/builder": "^5.0.0-alpha", "rollup": "^0.50.0" } diff --git a/packages/text-bitmap/src/BitmapFontLoader.js b/packages/text-bitmap/src/BitmapFontLoader.js new file mode 100644 index 0000000..3956060 --- /dev/null +++ b/packages/text-bitmap/src/BitmapFontLoader.js @@ -0,0 +1,112 @@ +import * as path from 'path'; +import { TextureCache } from '@pixi/utils'; +import { Resource, Loader } from '@pixi/loaders'; +import BitmapText from './BitmapText'; + +/** + * {@link PIXI.loaders.Loader Loader} middleware for loading + * bitmap-based fonts suitable for using with {@link PIXI.BitmapText}. + * @class + * @memberof PIXI + */ +export default class BitmapFontLoader +{ + /** + * Register a BitmapText font from loader resource. + * + * @param {PIXI.loaders.Resource} resource - Loader resource. + * @param {PIXI.Texture} texture - Reference to texture. + */ + static parse(resource, texture) + { + resource.bitmapFont = BitmapText.registerFont(resource.data, texture); + } + + /** + * Middleware function to support BitmapText fonts, this is automatically installed. + * @see PIXI.loaders.Loader.useMiddleware + */ + static middleware() + { + return function bitmapFontLoader(resource, next) + { + // skip if no data or not xml data + if (!resource.data || resource.type !== Resource.TYPE.XML) + { + next(); + + return; + } + + // skip if not bitmap font data, using some silly duck-typing + if (resource.data.getElementsByTagName('page').length === 0 + || resource.data.getElementsByTagName('info').length === 0 + || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null + ) + { + next(); + + return; + } + + let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; + + if (resource.isDataUrl) + { + if (xmlUrl === '.') + { + xmlUrl = ''; + } + + if (this.baseUrl && xmlUrl) + { + // if baseurl has a trailing slash then add one to xmlUrl so the replace works below + if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') + { + xmlUrl += '/'; + } + } + } + + // remove baseUrl from xmlUrl + xmlUrl = xmlUrl.replace(this.baseUrl, ''); + + // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. + if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') + { + xmlUrl += '/'; + } + + const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); + + if (TextureCache[textureUrl]) + { + // reuse existing texture + BitmapFontLoader.parse(resource, TextureCache[textureUrl]); + next(); + } + else + { + const loadOptions = { + crossOrigin: resource.crossOrigin, + loadType: Resource.LOAD_TYPE.IMAGE, + metadata: resource.metadata.imageMetadata, + parentResource: resource, + }; + + // load the texture for the font + this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => + { + BitmapFontLoader.parse(resource, res.texture); + next(); + }); + } + }; + } +} + +// Add custom add support for loading fnt files as XML +Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); + +// Install loader support for BitmapText +Loader.useMiddleware(BitmapFontLoader.middleware); diff --git a/packages/text-bitmap/src/index.js b/packages/text-bitmap/src/index.js index a98e7b4..e5afd2c 100644 --- a/packages/text-bitmap/src/index.js +++ b/packages/text-bitmap/src/index.js @@ -1 +1,2 @@ export { default as BitmapText } from './BitmapText'; +export { default as BitmapFontLoader } from './BitmapFontLoader'; diff --git a/packages/text-bitmap/test/.eslintrc.json b/packages/text-bitmap/test/.eslintrc.json new file mode 100644 index 0000000..2094b04 --- /dev/null +++ b/packages/text-bitmap/test/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "globals": { + "expect": false, + "assert": false, + "sinon": false, + "PIXI": false + }, + "rules": { + "func-names": 0, + "no-unused-expressions": 0 + } +} \ No newline at end of file diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/packages/loaders/src/spritesheetParser.js b/packages/loaders/src/spritesheetParser.js deleted file mode 100644 index f7fa206..0000000 --- a/packages/loaders/src/spritesheetParser.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Resource } from 'resource-loader'; -import url from 'url'; -import { Spritesheet } from '@pixi/spritesheet'; - -export default function () -{ - return function spritesheetParser(resource, next) - { - const imageResourceName = `${resource.name}_image`; - - // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists - if (!resource.data - || resource.type !== Resource.TYPE.JSON - || !resource.data.frames - || this.resources[imageResourceName] - ) - { - next(); - - return; - } - - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - const resourcePath = getResourcePath(resource, this.baseUrl); - - // load the image for this sheet - this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) - { - const spritesheet = new Spritesheet( - res.texture.baseTexture, - resource.data, - resource.url - ); - - spritesheet.parse(() => - { - resource.spritesheet = spritesheet; - resource.textures = spritesheet.textures; - next(); - }); - }); - }; -} - -export function getResourcePath(resource, baseUrl) -{ - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - return resource.data.meta.image; - } - - return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); -} diff --git a/packages/loaders/test/bitmapFontParser.js b/packages/loaders/test/bitmapFontParser.js deleted file mode 100644 index 43ef194..0000000 --- a/packages/loaders/test/bitmapFontParser.js +++ /dev/null @@ -1,325 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const { BitmapText } = require('@pixi/text-bitmap'); -const { BaseTextureCache, TextureCache } = require('@pixi/utils'); -const { Texture, BaseTexture } = require('@pixi/core'); -const { Spritesheet } = require('@pixi/spritesheet'); -const { bitmapFontParser, parseBitmapFontData } = require('../'); - -describe('PIXI.loaders.bitmapFontParser', function () -{ - afterEach(function () - { - for (const font in BitmapText.fonts) - { - delete BitmapText.fonts[font]; - } - for (const baseTexture in BaseTextureCache) - { - delete BaseTextureCache[baseTexture]; - } - for (const texture in TextureCache) - { - delete TextureCache[texture]; - } - }); - - before(function (done) - { - const resolveURL = (url) => path.resolve(this.resources, url); - - this.resources = path.join(__dirname, 'resources'); - this.fontXML = null; - this.fontScaledXML = null; - this.fontImage = null; - this.fontScaledImage = null; - this.atlasImage = null; - this.atlasScaledImage = null; - this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require - this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require - - const loadXML = (url) => new Promise((resolve) => - fs.readFile(resolveURL(url), 'utf8', (err, data) => - { - expect(err).to.be.null; - resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); - })); - - const loadImage = (url) => new Promise((resolve) => - { - const image = new Image(); - - image.onload = () => resolve(image); - image.src = resolveURL(url); - }); - - Promise.all([ - loadXML('font.fnt'), - loadXML('font@0.5x.fnt'), - loadImage('font.png'), - loadImage('font@0.5x.png'), - loadImage('atlas.png'), - loadImage('atlas@0.5x.png'), - ]).then(([ - fontXML, - fontScaledXML, - fontImage, - fontScaledImage, - atlasImage, - atlasScaledImage, - ]) => - { - this.fontXML = fontXML; - this.fontScaledXML = fontScaledXML; - this.fontImage = fontImage; - this.fontScaledImage = fontScaledImage; - this.atlasImage = atlasImage; - this.atlasScaledImage = atlasScaledImage; - done(); - }); - }); - - it('should exist and return a function', function () - { - expect(bitmapFontParser).to.be.a('function'); - expect(bitmapFontParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not XML', function () - { - const spy = sinon.spy(); - const res = {}; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is not properly formatted XML', function () - { - const spy = sinon.spy(); - const res = { data: document.createDocumentFragment() }; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - // TODO: Test the texture cache code path. - // TODO: Test the loading texture code path. - // TODO: Test data-url code paths. - - it('should properly register bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charA.texture.frame.x).to.equal(2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charB.texture.frame.x).to.equal(2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charC.texture.frame.x).to.equal(23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charD.texture.frame.x).to.equal(19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register SCALED bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); - const font = BitmapText.registerFont(this.fontScaledXML, 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register bitmap font NESTED into spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); - - it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); - - spritesheet.resolution = 1; - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); -}); - -describe('PIXI.loaders.parseBitmapFontData', function () -{ - it('should exist', function () - { - expect(parseBitmapFontData).to.be.a('function'); - }); - - // TODO: Test the parser code. -}); diff --git a/packages/loaders/test/index.js b/packages/loaders/test/index.js index f00de5f..72c6554 100644 --- a/packages/loaders/test/index.js +++ b/packages/loaders/test/index.js @@ -1,4 +1,2 @@ -require('./bitmapFontParser'); require('./loader'); -require('./spritesheetParser'); require('./textureParser'); diff --git a/packages/loaders/test/resources/atlas.json b/packages/loaders/test/resources/atlas.json deleted file mode 100644 index 86e65a5..0000000 --- a/packages/loaders/test/resources/atlas.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas.png b/packages/loaders/test/resources/atlas.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/atlas@0.5x.json b/packages/loaders/test/resources/atlas@0.5x.json deleted file mode 100644 index ae990a1..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas@0.5x.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas@0.5x.png b/packages/loaders/test/resources/atlas@0.5x.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font.fnt b/packages/loaders/test/resources/font.fnt deleted file mode 100644 index 56e1060..0000000 --- a/packages/loaders/test/resources/font.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font.png b/packages/loaders/test/resources/font.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font@0.5x.fnt b/packages/loaders/test/resources/font@0.5x.fnt deleted file mode 100644 index 6c247c7..0000000 --- a/packages/loaders/test/resources/font@0.5x.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font@0.5x.png b/packages/loaders/test/resources/font@0.5x.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/spritesheetParser.js b/packages/loaders/test/spritesheetParser.js deleted file mode 100644 index 98890b8..0000000 --- a/packages/loaders/test/spritesheetParser.js +++ /dev/null @@ -1,213 +0,0 @@ -const path = require('path'); -const { spritesheetParser, Resource, Loader, getResourcePath } = require('../'); -const { Texture, BaseTexture } = require('@pixi/core'); - -describe('PIXI.loaders.spritesheetParser', function () -{ - it('should exist and return a function', function () - { - expect(spritesheetParser).to.be.a('function'); - expect(spritesheetParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not JSON', function () - { - const spy = sinon.spy(); - const res = {}; - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is JSON, but improper format', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, {}); - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should load the image & create textures if json is properly formatted', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); - const loader = new Loader(); - const addStub = sinon.stub(loader, 'add'); - const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); - - imgRes.texture = new Texture(new BaseTexture(imgRes.data)); - - addStub.yields(imgRes); - - spritesheetParser().call(loader, res, spy); - - addStub.restore(); - - expect(spy).to.have.been.calledOnce; - expect(addStub).to.have.been.calledWith( - `${res.name}_image`, - `${path.dirname(res.url)}/${res.data.meta.image}` - ); - expect(res).to.have.property('textures') - .that.is.an('object') - .with.keys(Object.keys(getJsonSpritesheet().frames)) - .and.has.property('0.png') - .that.is.an.instanceof(Texture); - }); - - it('should build the image url', function () - { - function getPath(url, image) - { - return getResourcePath({ - url, - data: { meta: { image } }, - }); - } - - let result = getPath('http://some.com/spritesheet.json', 'img.png'); - - expect(result).to.be.equals('http://some.com/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('http://some.com/some/img.png'); - - result = getPath('/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/img.png'); - - result = getPath('/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('/some/img.png'); - }); - - // TODO: Test that rectangles are created correctly. - // TODO: Test that bathc processing works correctly. - // TODO: Test that resolution processing works correctly. - // TODO: Test that metadata is honored. -}); - -function createMockResource(type, data) -{ - const name = `${Math.floor(Date.now() * Math.random())}`; - - return { - url: `http://localhost/doesnt_exist/${name}`, - name, - type, - data, - metadata: {}, - }; -} - -function getJsonSpritesheet() -{ - /* eslint-disable */ - return {"frames": { - "0.png": - { - "frame": {"x":14,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "1.png": - { - "frame": {"x":14,"y":42,"w":12,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, - "sourceSize": {"w":12,"h":14} - }, - "2.png": - { - "frame": {"x":14,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "3.png": - { - "frame": {"x":42,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "4.png": - { - "frame": {"x":28,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "5.png": - { - "frame": {"x":14,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "6.png": - { - "frame": {"x":0,"y":42,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "7.png": - { - "frame": {"x":0,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "8.png": - { - "frame": {"x":0,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "9.png": - { - "frame": {"x":0,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }}, - "meta": { - "app": "http://www.texturepacker.com", - "version": "1.0", - "image": "hud.png", - "format": "RGBA8888", - "size": {"w":64,"h":64}, - "scale": "1", - "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" - } - }; - /* eslint-enable */ -} diff --git a/packages/mixin-app-loader/LICENSE b/packages/mixin-app-loader/LICENSE new file mode 100644 index 0000000..5d16b50 --- /dev/null +++ b/packages/mixin-app-loader/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2013-2017 Mathew Groves, Chad Engler + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/packages/mixin-app-loader/README.md b/packages/mixin-app-loader/README.md new file mode 100644 index 0000000..3e89f86 --- /dev/null +++ b/packages/mixin-app-loader/README.md @@ -0,0 +1,13 @@ +# @pixi/mixin-app-loader + +## Installation + +```bash +npm install @pixi/mixin-app-loader +``` + +## Usage + +```js +import '@pixi/mixin-app-loader'; +``` \ No newline at end of file diff --git a/packages/mixin-app-loader/package.json b/packages/mixin-app-loader/package.json new file mode 100644 index 0000000..869ed4e --- /dev/null +++ b/packages/mixin-app-loader/package.json @@ -0,0 +1,41 @@ +{ + "name": "@pixi/mixin-app-loader", + "version": "5.0.0-alpha", + "main": "lib/mixin-app-loader.js", + "module": "lib/mixin-app-loader.es.js", + "description": "Support for loader in Application", + "author": "Mat Groves", + "contributors": [ + "Matt Karl " + ], + "homepage": "http://pixijs.com/", + "bugs": "https://github.com/pixijs/pixi.js/issues", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/pixijs/pixi.js.git" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "build:dev": "rollup -c", + "build": "rollup -cp", + "watch": "rollup -cw", + "postversion": "npm run build", + "test": "tester" + }, + "files": [ + "lib" + ], + "dependencies": { + "@pixi/app": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" + }, + "devDependencies": { + "@pixi/utils": "^5.0.0-alpha", + "@internal/tester": "^5.0.0-alpha", + "@internal/builder": "^5.0.0-alpha", + "rollup": "^0.50.0" + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/rollup.config.js b/packages/mixin-app-loader/rollup.config.js new file mode 100644 index 0000000..a73c874 --- /dev/null +++ b/packages/mixin-app-loader/rollup.config.js @@ -0,0 +1 @@ +export { default } from '@internal/builder'; diff --git a/packages/mixin-app-loader/src/index.js b/packages/mixin-app-loader/src/index.js new file mode 100644 index 0000000..354997a --- /dev/null +++ b/packages/mixin-app-loader/src/index.js @@ -0,0 +1,38 @@ +import { Application } from '@pixi/app'; +import { Loader, shared } from '@pixi/loaders'; + +Application.prototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperties(Application.prototype, { + loader: { + get() + { + if (!this._loader && this._options) + { + const { sharedLoader } = this._options; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +Application.prototype._parentDestroy = Application.prototype.destroy; +Application.prototype.destroy = function destroy(removeView) +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(removeView); +}; diff --git a/packages/mixin-app-loader/test/.eslintrc.json b/packages/mixin-app-loader/test/.eslintrc.json new file mode 100644 index 0000000..2094b04 --- /dev/null +++ b/packages/mixin-app-loader/test/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "globals": { + "expect": false, + "assert": false, + "sinon": false, + "PIXI": false + }, + "rules": { + "func-names": 0, + "no-unused-expressions": 0 + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/test/index.js b/packages/mixin-app-loader/test/index.js new file mode 100644 index 0000000..3468fe5 --- /dev/null +++ b/packages/mixin-app-loader/test/index.js @@ -0,0 +1,35 @@ +const { Application } = require('@pixi/app'); +const { Loader, shared } = require('@pixi/loaders'); +const { skipHello } = require('@pixi/utils'); + +require('../'); + +skipHello(); + +describe('PIXI.Application#loader', function () +{ + it('should contain loader property', function () + { + const obj = new Application(); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); + + it('should use sharedLoader option', function () + { + const obj = new Application({ sharedLoader: true }); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + expect(obj.loader).to.equal(shared); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); +}); diff --git a/packages/spritesheet/package.json b/packages/spritesheet/package.json index 34749e2..5f35442 100644 --- a/packages/spritesheet/package.json +++ b/packages/spritesheet/package.json @@ -31,7 +31,8 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/math": "^5.0.0-alpha", - "@pixi/utils": "^5.0.0-alpha" + "@pixi/utils": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" }, "devDependencies": { "@internal/builder": "^5.0.0-alpha", diff --git a/packages/spritesheet/src/SpritesheetLoader.js b/packages/spritesheet/src/SpritesheetLoader.js new file mode 100644 index 0000000..c7b34ea --- /dev/null +++ b/packages/spritesheet/src/SpritesheetLoader.js @@ -0,0 +1,83 @@ +import url from 'url'; +import { Resource, Loader } from '@pixi/loaders'; +import Spritesheet from './Spritesheet'; + +/** + * {@link PIXI.loaders.Loader Loader} middleware for loading + * texture atlases that have been created with TexturePacker or + * similar JSON-based spritesheet. This automatically generates + * Texture resources. + * @class + * @memberof PIXI + */ +export default class SpritesheetLoader +{ + /** + * Middleware function to support Spritesheets, this is automatically installed. + * @see PIXI.loaders.Loader.useMiddleware + */ + static middleware() + { + return function spritesheetLoader(resource, next) + { + const imageResourceName = `${resource.name}_image`; + + // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists + if (!resource.data + || resource.type !== Resource.TYPE.JSON + || !resource.data.frames + || this.resources[imageResourceName] + ) + { + next(); + + return; + } + + const loadOptions = { + crossOrigin: resource.crossOrigin, + loadType: Resource.LOAD_TYPE.IMAGE, + metadata: resource.metadata.imageMetadata, + parentResource: resource, + }; + + const resourcePath = SpritesheetLoader.getResourcePath(resource, this.baseUrl); + + // load the image for this sheet + this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) + { + const spritesheet = new Spritesheet( + res.texture.baseTexture, + resource.data, + resource.url + ); + + spritesheet.parse(() => + { + resource.spritesheet = spritesheet; + resource.textures = spritesheet.textures; + next(); + }); + }); + }; + } + + /** + * Get the spritesheets root path + * @param {PIXI.loader.Resource} resource - Resource to check path + * @param {string} baseUrl - Base root url + */ + static getResourcePath(resource, baseUrl) + { + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); + } +} + +// Install loader support for Spritesheet objects +Loader.useMiddleware(SpritesheetLoader.middleware); diff --git a/packages/spritesheet/src/index.js b/packages/spritesheet/src/index.js index 51d96a1..b9ee1e8 100644 --- a/packages/spritesheet/src/index.js +++ b/packages/spritesheet/src/index.js @@ -1 +1,2 @@ export { default as Spritesheet } from './Spritesheet'; +export { default as SpritesheetLoader } from './SpritesheetLoader'; diff --git a/packages/spritesheet/test/Spritesheet.js b/packages/spritesheet/test/Spritesheet.js new file mode 100644 index 0000000..35d8688 --- /dev/null +++ b/packages/spritesheet/test/Spritesheet.js @@ -0,0 +1,105 @@ +const { Spritesheet } = require('../'); +const { BaseTexture, Texture } = require('@pixi/core'); +const path = require('path'); + +describe('PIXI.Spritesheet', function () +{ + before(function () + { + this.resources = path.join(__dirname, 'resources'); + this.validate = function (spritesheet, done) + { + spritesheet.parse(function (textures) + { + const id = 'goldmine_10_5.png'; + const width = Math.floor(spritesheet.data.frames[id].frame.w); + const height = Math.floor(spritesheet.data.frames[id].frame.h); + + expect(Object.keys(textures).length).to.equal(1); + expect(Object.keys(spritesheet.textures).length).to.equal(1); + expect(textures[id]).to.be.an.instanceof(Texture); + expect(textures[id].width).to.equal(width / spritesheet.resolution); + expect(textures[id].height).to.equal(height / spritesheet.resolution); + expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); + spritesheet.destroy(true); + expect(spritesheet.textures).to.be.null; + expect(spritesheet.baseTexture).to.be.null; + done(); + }); + }; + }); + + it('should exist on PIXI', function () + { + expect(Spritesheet).to.be.a.function; + expect(Spritesheet.BATCH_SIZE).to.be.a.number; + }); + + it('should create an instance', function () + { + const baseTexture = new BaseTexture(); + const data = { + frames: {}, + meta: {}, + }; + + const spritesheet = new Spritesheet(baseTexture, data); + + expect(spritesheet.data).to.equal(data); + expect(spritesheet.baseTexture).to.equal(baseTexture); + expect(spritesheet.resolution).to.equal(1); + + spritesheet.destroy(true); + }); + + it('should create instance with scale resolution', function (done) + { + const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require + const image = new Image(); + + image.src = path.join(this.resources, data.meta.image); + image.onload = () => + { + const baseTexture = new BaseTexture(image, null, 1); + const spritesheet = new Spritesheet(baseTexture, data); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1.png'); + expect(spritesheet.resolution).to.equal(0.5); + this.validate(spritesheet, done); + }; + }); + + it('should create instance with BaseTexture source scale', function (done) + { + const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require + const baseTexture = new BaseTexture.fromImage(data.meta.image, undefined, undefined, 1.5); + const spritesheet = new Spritesheet(baseTexture, data); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1.png'); + expect(spritesheet.resolution).to.equal(0.5); + + this.validate(spritesheet, done); + }); + + it('should create instance with filename resolution', function (done) + { + const uri = path.resolve(this.resources, 'building1@2x.json'); + const data = require(uri); // eslint-disable-line global-require + const image = new Image(); + + image.src = path.join(this.resources, data.meta.image); + image.onload = () => + { + const baseTexture = new BaseTexture(image, null, 1); + const spritesheet = new Spritesheet(baseTexture, data, uri); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1@2x.png'); + expect(spritesheet.resolution).to.equal(2); + + this.validate(spritesheet, done); + }; + }); +}); diff --git a/packages/spritesheet/test/SpritesheetLoader.js b/packages/spritesheet/test/SpritesheetLoader.js new file mode 100644 index 0000000..e4205ec --- /dev/null +++ b/packages/spritesheet/test/SpritesheetLoader.js @@ -0,0 +1,214 @@ +const path = require('path'); +const { Loader, Resource } = require('@pixi/loaders'); +const { Texture, BaseTexture } = require('@pixi/core'); +const { SpritesheetLoader } = require('../'); + +describe('PIXI.SpritesheetLoader', function () +{ + it('should exist and return a function', function () + { + expect(SpritesheetLoader.middleware).to.be.a('function'); + expect(SpritesheetLoader.middleware()).to.be.a('function'); + }); + + it('should do nothing if the resource is not JSON', function () + { + const spy = sinon.spy(); + const res = {}; + + SpritesheetLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should do nothing if the resource is JSON, but improper format', function () + { + const spy = sinon.spy(); + const res = createMockResource(Resource.TYPE.JSON, {}); + + SpritesheetLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should load the image & create textures if json is properly formatted', function () + { + const spy = sinon.spy(); + const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); + const loader = new Loader(); + const addStub = sinon.stub(loader, 'add'); + const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); + + imgRes.texture = new Texture(new BaseTexture(imgRes.data)); + + addStub.yields(imgRes); + + SpritesheetLoader.middleware().call(loader, res, spy); + + addStub.restore(); + + expect(spy).to.have.been.calledOnce; + expect(addStub).to.have.been.calledWith( + `${res.name}_image`, + `${path.dirname(res.url)}/${res.data.meta.image}` + ); + expect(res).to.have.property('textures') + .that.is.an('object') + .with.keys(Object.keys(getJsonSpritesheet().frames)) + .and.has.property('0.png') + .that.is.an.instanceof(Texture); + }); + + it('should build the image url', function () + { + function getPath(url, image) + { + return SpritesheetLoader.getResourcePath({ + url, + data: { meta: { image } }, + }); + } + + let result = getPath('http://some.com/spritesheet.json', 'img.png'); + + expect(result).to.be.equals('http://some.com/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); + expect(result).to.be.equals('http://some.com/some/dir/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); + expect(result).to.be.equals('http://some.com/some/dir/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); + expect(result).to.be.equals('http://some.com/some/img.png'); + + result = getPath('/spritesheet.json', 'img.png'); + expect(result).to.be.equals('/img.png'); + + result = getPath('/some/dir/spritesheet.json', 'img.png'); + expect(result).to.be.equals('/some/dir/img.png'); + + result = getPath('/some/dir/spritesheet.json', './img.png'); + expect(result).to.be.equals('/some/dir/img.png'); + + result = getPath('/some/dir/spritesheet.json', '../img.png'); + expect(result).to.be.equals('/some/img.png'); + }); + + // TODO: Test that rectangles are created correctly. + // TODO: Test that bathc processing works correctly. + // TODO: Test that resolution processing works correctly. + // TODO: Test that metadata is honored. +}); + +function createMockResource(type, data) +{ + const name = `${Math.floor(Date.now() * Math.random())}`; + + return { + url: `http://localhost/doesnt_exist/${name}`, + name, + type, + data, + metadata: {}, + }; +} + +function getJsonSpritesheet() +{ + /* eslint-disable */ + return {"frames": { + "0.png": + { + "frame": {"x":14,"y":28,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "1.png": + { + "frame": {"x":14,"y":42,"w":12,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, + "sourceSize": {"w":12,"h":14} + }, + "2.png": + { + "frame": {"x":14,"y":14,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "3.png": + { + "frame": {"x":42,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "4.png": + { + "frame": {"x":28,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "5.png": + { + "frame": {"x":14,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "6.png": + { + "frame": {"x":0,"y":42,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "7.png": + { + "frame": {"x":0,"y":28,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "8.png": + { + "frame": {"x":0,"y":14,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "9.png": + { + "frame": {"x":0,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }}, + "meta": { + "app": "http://www.texturepacker.com", + "version": "1.0", + "image": "hud.png", + "format": "RGBA8888", + "size": {"w":64,"h":64}, + "scale": "1", + "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" + } + }; + /* eslint-enable */ +} diff --git a/packages/spritesheet/test/index.js b/packages/spritesheet/test/index.js index 35d8688..8cf5636 100644 --- a/packages/spritesheet/test/index.js +++ b/packages/spritesheet/test/index.js @@ -1,105 +1,2 @@ -const { Spritesheet } = require('../'); -const { BaseTexture, Texture } = require('@pixi/core'); -const path = require('path'); - -describe('PIXI.Spritesheet', function () -{ - before(function () - { - this.resources = path.join(__dirname, 'resources'); - this.validate = function (spritesheet, done) - { - spritesheet.parse(function (textures) - { - const id = 'goldmine_10_5.png'; - const width = Math.floor(spritesheet.data.frames[id].frame.w); - const height = Math.floor(spritesheet.data.frames[id].frame.h); - - expect(Object.keys(textures).length).to.equal(1); - expect(Object.keys(spritesheet.textures).length).to.equal(1); - expect(textures[id]).to.be.an.instanceof(Texture); - expect(textures[id].width).to.equal(width / spritesheet.resolution); - expect(textures[id].height).to.equal(height / spritesheet.resolution); - expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); - spritesheet.destroy(true); - expect(spritesheet.textures).to.be.null; - expect(spritesheet.baseTexture).to.be.null; - done(); - }); - }; - }); - - it('should exist on PIXI', function () - { - expect(Spritesheet).to.be.a.function; - expect(Spritesheet.BATCH_SIZE).to.be.a.number; - }); - - it('should create an instance', function () - { - const baseTexture = new BaseTexture(); - const data = { - frames: {}, - meta: {}, - }; - - const spritesheet = new Spritesheet(baseTexture, data); - - expect(spritesheet.data).to.equal(data); - expect(spritesheet.baseTexture).to.equal(baseTexture); - expect(spritesheet.resolution).to.equal(1); - - spritesheet.destroy(true); - }); - - it('should create instance with scale resolution', function (done) - { - const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require - const image = new Image(); - - image.src = path.join(this.resources, data.meta.image); - image.onload = () => - { - const baseTexture = new BaseTexture(image, null, 1); - const spritesheet = new Spritesheet(baseTexture, data); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1.png'); - expect(spritesheet.resolution).to.equal(0.5); - this.validate(spritesheet, done); - }; - }); - - it('should create instance with BaseTexture source scale', function (done) - { - const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require - const baseTexture = new BaseTexture.fromImage(data.meta.image, undefined, undefined, 1.5); - const spritesheet = new Spritesheet(baseTexture, data); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1.png'); - expect(spritesheet.resolution).to.equal(0.5); - - this.validate(spritesheet, done); - }); - - it('should create instance with filename resolution', function (done) - { - const uri = path.resolve(this.resources, 'building1@2x.json'); - const data = require(uri); // eslint-disable-line global-require - const image = new Image(); - - image.src = path.join(this.resources, data.meta.image); - image.onload = () => - { - const baseTexture = new BaseTexture(image, null, 1); - const spritesheet = new Spritesheet(baseTexture, data, uri); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1@2x.png'); - expect(spritesheet.resolution).to.equal(2); - - this.validate(spritesheet, done); - }; - }); -}); +require('./Spritesheet'); +require('./SpritesheetLoader'); diff --git a/packages/text-bitmap/package.json b/packages/text-bitmap/package.json index a8a5f95..4b47da6 100644 --- a/packages/text-bitmap/package.json +++ b/packages/text-bitmap/package.json @@ -23,7 +23,7 @@ "build": "rollup -cp", "watch": "rollup -cw", "postversion": "npm run build", - "test": "exit 0" + "test": "tester" }, "files": [ "lib" @@ -31,12 +31,15 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha", "@pixi/sprite": "^5.0.0-alpha", "@pixi/settings": "^5.0.0-alpha", "@pixi/math": "^5.0.0-alpha", "@pixi/display": "^5.0.0-alpha" }, "devDependencies": { + "@pixi/spritesheet": "^5.0.0-alpha", + "@internal/tester": "^5.0.0-alpha", "@internal/builder": "^5.0.0-alpha", "rollup": "^0.50.0" } diff --git a/packages/text-bitmap/src/BitmapFontLoader.js b/packages/text-bitmap/src/BitmapFontLoader.js new file mode 100644 index 0000000..3956060 --- /dev/null +++ b/packages/text-bitmap/src/BitmapFontLoader.js @@ -0,0 +1,112 @@ +import * as path from 'path'; +import { TextureCache } from '@pixi/utils'; +import { Resource, Loader } from '@pixi/loaders'; +import BitmapText from './BitmapText'; + +/** + * {@link PIXI.loaders.Loader Loader} middleware for loading + * bitmap-based fonts suitable for using with {@link PIXI.BitmapText}. + * @class + * @memberof PIXI + */ +export default class BitmapFontLoader +{ + /** + * Register a BitmapText font from loader resource. + * + * @param {PIXI.loaders.Resource} resource - Loader resource. + * @param {PIXI.Texture} texture - Reference to texture. + */ + static parse(resource, texture) + { + resource.bitmapFont = BitmapText.registerFont(resource.data, texture); + } + + /** + * Middleware function to support BitmapText fonts, this is automatically installed. + * @see PIXI.loaders.Loader.useMiddleware + */ + static middleware() + { + return function bitmapFontLoader(resource, next) + { + // skip if no data or not xml data + if (!resource.data || resource.type !== Resource.TYPE.XML) + { + next(); + + return; + } + + // skip if not bitmap font data, using some silly duck-typing + if (resource.data.getElementsByTagName('page').length === 0 + || resource.data.getElementsByTagName('info').length === 0 + || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null + ) + { + next(); + + return; + } + + let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; + + if (resource.isDataUrl) + { + if (xmlUrl === '.') + { + xmlUrl = ''; + } + + if (this.baseUrl && xmlUrl) + { + // if baseurl has a trailing slash then add one to xmlUrl so the replace works below + if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') + { + xmlUrl += '/'; + } + } + } + + // remove baseUrl from xmlUrl + xmlUrl = xmlUrl.replace(this.baseUrl, ''); + + // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. + if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') + { + xmlUrl += '/'; + } + + const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); + + if (TextureCache[textureUrl]) + { + // reuse existing texture + BitmapFontLoader.parse(resource, TextureCache[textureUrl]); + next(); + } + else + { + const loadOptions = { + crossOrigin: resource.crossOrigin, + loadType: Resource.LOAD_TYPE.IMAGE, + metadata: resource.metadata.imageMetadata, + parentResource: resource, + }; + + // load the texture for the font + this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => + { + BitmapFontLoader.parse(resource, res.texture); + next(); + }); + } + }; + } +} + +// Add custom add support for loading fnt files as XML +Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); + +// Install loader support for BitmapText +Loader.useMiddleware(BitmapFontLoader.middleware); diff --git a/packages/text-bitmap/src/index.js b/packages/text-bitmap/src/index.js index a98e7b4..e5afd2c 100644 --- a/packages/text-bitmap/src/index.js +++ b/packages/text-bitmap/src/index.js @@ -1 +1,2 @@ export { default as BitmapText } from './BitmapText'; +export { default as BitmapFontLoader } from './BitmapFontLoader'; diff --git a/packages/text-bitmap/test/.eslintrc.json b/packages/text-bitmap/test/.eslintrc.json new file mode 100644 index 0000000..2094b04 --- /dev/null +++ b/packages/text-bitmap/test/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "globals": { + "expect": false, + "assert": false, + "sinon": false, + "PIXI": false + }, + "rules": { + "func-names": 0, + "no-unused-expressions": 0 + } +} \ No newline at end of file diff --git a/packages/text-bitmap/test/BitmapFontLoader.js b/packages/text-bitmap/test/BitmapFontLoader.js new file mode 100644 index 0000000..a76fc1c --- /dev/null +++ b/packages/text-bitmap/test/BitmapFontLoader.js @@ -0,0 +1,319 @@ +const path = require('path'); +const fs = require('fs'); +const { BaseTextureCache, TextureCache } = require('@pixi/utils'); +const { Texture, BaseTexture } = require('@pixi/core'); +const { Spritesheet } = require('@pixi/spritesheet'); +const { BitmapText, BitmapFontLoader } = require('../'); + +describe('PIXI.BitmapFontLoader', function () +{ + afterEach(function () + { + for (const font in BitmapText.fonts) + { + delete BitmapText.fonts[font]; + } + for (const baseTexture in BaseTextureCache) + { + delete BaseTextureCache[baseTexture]; + } + for (const texture in TextureCache) + { + delete TextureCache[texture]; + } + }); + + before(function (done) + { + const resolveURL = (url) => path.resolve(this.resources, url); + + this.resources = path.join(__dirname, 'resources'); + this.fontXML = null; + this.fontScaledXML = null; + this.fontImage = null; + this.fontScaledImage = null; + this.atlasImage = null; + this.atlasScaledImage = null; + this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require + this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require + + const loadXML = (url) => new Promise((resolve) => + fs.readFile(resolveURL(url), 'utf8', (err, data) => + { + expect(err).to.be.null; + resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); + })); + + const loadImage = (url) => new Promise((resolve) => + { + const image = new Image(); + + image.onload = () => resolve(image); + image.src = resolveURL(url); + }); + + Promise.all([ + loadXML('font.fnt'), + loadXML('font@0.5x.fnt'), + loadImage('font.png'), + loadImage('font@0.5x.png'), + loadImage('atlas.png'), + loadImage('atlas@0.5x.png'), + ]).then(([ + fontXML, + fontScaledXML, + fontImage, + fontScaledImage, + atlasImage, + atlasScaledImage, + ]) => + { + this.fontXML = fontXML; + this.fontScaledXML = fontScaledXML; + this.fontImage = fontImage; + this.fontScaledImage = fontScaledImage; + this.atlasImage = atlasImage; + this.atlasScaledImage = atlasScaledImage; + done(); + }); + }); + + it('should exist and return a function', function () + { + expect(BitmapFontLoader.middleware).to.be.a('function'); + expect(BitmapFontLoader.middleware()).to.be.a('function'); + }); + + it('should do nothing if the resource is not XML', function () + { + const spy = sinon.spy(); + const res = {}; + + BitmapFontLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should do nothing if the resource is not properly formatted XML', function () + { + const spy = sinon.spy(); + const res = { data: document.createDocumentFragment() }; + + BitmapFontLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + // TODO: Test the texture cache code path. + // TODO: Test the loading texture code path. + // TODO: Test data-url code paths. + + it('should properly register bitmap font', function (done) + { + const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); + 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charA.texture.frame.x).to.equal(2); + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charB.texture.frame.x).to.equal(2); + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charC.texture.frame.x).to.equal(23); + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charD.texture.frame.x).to.equal(19); + 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]; + + expect(charE).to.be.undefined; + done(); + }); + + it('should properly register SCALED bitmap font', function (done) + { + const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); + const font = BitmapText.registerFont(this.fontScaledXML, 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 + 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]; + + expect(charE).to.be.undefined; + done(); + }); + + it('should properly register bitmap font NESTED into spritesheet', function (done) + { + const baseTexture = new BaseTexture(this.atlasImage, null, 1); + const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); + + spritesheet.parse(() => + { + const fontTexture = Texture.fromFrame('resources/font.png'); + const font = BitmapText.registerFont(this.fontXML, fontTexture); + const fontX = 158; // bare value from spritesheet frame + const fontY = 2; // bare value from spritesheet frame + + 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charA.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charB.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charC.texture.frame.x).to.equal(fontX + 23); + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charD.texture.frame.x).to.equal(fontX + 19); + 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]; + + expect(charE).to.be.undefined; + done(); + }); + }); + + it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) + { + const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); + const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); + + spritesheet.resolution = 1; + + spritesheet.parse(() => + { + const fontTexture = Texture.fromFrame('resources/font.png'); + const font = BitmapText.registerFont(this.fontXML, fontTexture); + const fontX = 158; // bare value from spritesheet frame + const fontY = 2; // bare value from spritesheet frame + + 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charA.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charB.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charC.texture.frame.x).to.equal(fontX + 23); + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charD.texture.frame.x).to.equal(fontX + 19); + 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]; + + expect(charE).to.be.undefined; + done(); + }); + }); + + it('should parse exist', function () + { + expect(BitmapFontLoader.parse).to.be.a('function'); + }); +}); diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/packages/loaders/src/spritesheetParser.js b/packages/loaders/src/spritesheetParser.js deleted file mode 100644 index f7fa206..0000000 --- a/packages/loaders/src/spritesheetParser.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Resource } from 'resource-loader'; -import url from 'url'; -import { Spritesheet } from '@pixi/spritesheet'; - -export default function () -{ - return function spritesheetParser(resource, next) - { - const imageResourceName = `${resource.name}_image`; - - // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists - if (!resource.data - || resource.type !== Resource.TYPE.JSON - || !resource.data.frames - || this.resources[imageResourceName] - ) - { - next(); - - return; - } - - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - const resourcePath = getResourcePath(resource, this.baseUrl); - - // load the image for this sheet - this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) - { - const spritesheet = new Spritesheet( - res.texture.baseTexture, - resource.data, - resource.url - ); - - spritesheet.parse(() => - { - resource.spritesheet = spritesheet; - resource.textures = spritesheet.textures; - next(); - }); - }); - }; -} - -export function getResourcePath(resource, baseUrl) -{ - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - return resource.data.meta.image; - } - - return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); -} diff --git a/packages/loaders/test/bitmapFontParser.js b/packages/loaders/test/bitmapFontParser.js deleted file mode 100644 index 43ef194..0000000 --- a/packages/loaders/test/bitmapFontParser.js +++ /dev/null @@ -1,325 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const { BitmapText } = require('@pixi/text-bitmap'); -const { BaseTextureCache, TextureCache } = require('@pixi/utils'); -const { Texture, BaseTexture } = require('@pixi/core'); -const { Spritesheet } = require('@pixi/spritesheet'); -const { bitmapFontParser, parseBitmapFontData } = require('../'); - -describe('PIXI.loaders.bitmapFontParser', function () -{ - afterEach(function () - { - for (const font in BitmapText.fonts) - { - delete BitmapText.fonts[font]; - } - for (const baseTexture in BaseTextureCache) - { - delete BaseTextureCache[baseTexture]; - } - for (const texture in TextureCache) - { - delete TextureCache[texture]; - } - }); - - before(function (done) - { - const resolveURL = (url) => path.resolve(this.resources, url); - - this.resources = path.join(__dirname, 'resources'); - this.fontXML = null; - this.fontScaledXML = null; - this.fontImage = null; - this.fontScaledImage = null; - this.atlasImage = null; - this.atlasScaledImage = null; - this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require - this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require - - const loadXML = (url) => new Promise((resolve) => - fs.readFile(resolveURL(url), 'utf8', (err, data) => - { - expect(err).to.be.null; - resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); - })); - - const loadImage = (url) => new Promise((resolve) => - { - const image = new Image(); - - image.onload = () => resolve(image); - image.src = resolveURL(url); - }); - - Promise.all([ - loadXML('font.fnt'), - loadXML('font@0.5x.fnt'), - loadImage('font.png'), - loadImage('font@0.5x.png'), - loadImage('atlas.png'), - loadImage('atlas@0.5x.png'), - ]).then(([ - fontXML, - fontScaledXML, - fontImage, - fontScaledImage, - atlasImage, - atlasScaledImage, - ]) => - { - this.fontXML = fontXML; - this.fontScaledXML = fontScaledXML; - this.fontImage = fontImage; - this.fontScaledImage = fontScaledImage; - this.atlasImage = atlasImage; - this.atlasScaledImage = atlasScaledImage; - done(); - }); - }); - - it('should exist and return a function', function () - { - expect(bitmapFontParser).to.be.a('function'); - expect(bitmapFontParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not XML', function () - { - const spy = sinon.spy(); - const res = {}; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is not properly formatted XML', function () - { - const spy = sinon.spy(); - const res = { data: document.createDocumentFragment() }; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - // TODO: Test the texture cache code path. - // TODO: Test the loading texture code path. - // TODO: Test data-url code paths. - - it('should properly register bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charA.texture.frame.x).to.equal(2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charB.texture.frame.x).to.equal(2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charC.texture.frame.x).to.equal(23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charD.texture.frame.x).to.equal(19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register SCALED bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); - const font = BitmapText.registerFont(this.fontScaledXML, 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register bitmap font NESTED into spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); - - it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); - - spritesheet.resolution = 1; - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); -}); - -describe('PIXI.loaders.parseBitmapFontData', function () -{ - it('should exist', function () - { - expect(parseBitmapFontData).to.be.a('function'); - }); - - // TODO: Test the parser code. -}); diff --git a/packages/loaders/test/index.js b/packages/loaders/test/index.js index f00de5f..72c6554 100644 --- a/packages/loaders/test/index.js +++ b/packages/loaders/test/index.js @@ -1,4 +1,2 @@ -require('./bitmapFontParser'); require('./loader'); -require('./spritesheetParser'); require('./textureParser'); diff --git a/packages/loaders/test/resources/atlas.json b/packages/loaders/test/resources/atlas.json deleted file mode 100644 index 86e65a5..0000000 --- a/packages/loaders/test/resources/atlas.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas.png b/packages/loaders/test/resources/atlas.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/atlas@0.5x.json b/packages/loaders/test/resources/atlas@0.5x.json deleted file mode 100644 index ae990a1..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas@0.5x.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas@0.5x.png b/packages/loaders/test/resources/atlas@0.5x.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font.fnt b/packages/loaders/test/resources/font.fnt deleted file mode 100644 index 56e1060..0000000 --- a/packages/loaders/test/resources/font.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font.png b/packages/loaders/test/resources/font.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font@0.5x.fnt b/packages/loaders/test/resources/font@0.5x.fnt deleted file mode 100644 index 6c247c7..0000000 --- a/packages/loaders/test/resources/font@0.5x.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font@0.5x.png b/packages/loaders/test/resources/font@0.5x.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/spritesheetParser.js b/packages/loaders/test/spritesheetParser.js deleted file mode 100644 index 98890b8..0000000 --- a/packages/loaders/test/spritesheetParser.js +++ /dev/null @@ -1,213 +0,0 @@ -const path = require('path'); -const { spritesheetParser, Resource, Loader, getResourcePath } = require('../'); -const { Texture, BaseTexture } = require('@pixi/core'); - -describe('PIXI.loaders.spritesheetParser', function () -{ - it('should exist and return a function', function () - { - expect(spritesheetParser).to.be.a('function'); - expect(spritesheetParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not JSON', function () - { - const spy = sinon.spy(); - const res = {}; - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is JSON, but improper format', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, {}); - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should load the image & create textures if json is properly formatted', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); - const loader = new Loader(); - const addStub = sinon.stub(loader, 'add'); - const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); - - imgRes.texture = new Texture(new BaseTexture(imgRes.data)); - - addStub.yields(imgRes); - - spritesheetParser().call(loader, res, spy); - - addStub.restore(); - - expect(spy).to.have.been.calledOnce; - expect(addStub).to.have.been.calledWith( - `${res.name}_image`, - `${path.dirname(res.url)}/${res.data.meta.image}` - ); - expect(res).to.have.property('textures') - .that.is.an('object') - .with.keys(Object.keys(getJsonSpritesheet().frames)) - .and.has.property('0.png') - .that.is.an.instanceof(Texture); - }); - - it('should build the image url', function () - { - function getPath(url, image) - { - return getResourcePath({ - url, - data: { meta: { image } }, - }); - } - - let result = getPath('http://some.com/spritesheet.json', 'img.png'); - - expect(result).to.be.equals('http://some.com/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('http://some.com/some/img.png'); - - result = getPath('/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/img.png'); - - result = getPath('/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('/some/img.png'); - }); - - // TODO: Test that rectangles are created correctly. - // TODO: Test that bathc processing works correctly. - // TODO: Test that resolution processing works correctly. - // TODO: Test that metadata is honored. -}); - -function createMockResource(type, data) -{ - const name = `${Math.floor(Date.now() * Math.random())}`; - - return { - url: `http://localhost/doesnt_exist/${name}`, - name, - type, - data, - metadata: {}, - }; -} - -function getJsonSpritesheet() -{ - /* eslint-disable */ - return {"frames": { - "0.png": - { - "frame": {"x":14,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "1.png": - { - "frame": {"x":14,"y":42,"w":12,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, - "sourceSize": {"w":12,"h":14} - }, - "2.png": - { - "frame": {"x":14,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "3.png": - { - "frame": {"x":42,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "4.png": - { - "frame": {"x":28,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "5.png": - { - "frame": {"x":14,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "6.png": - { - "frame": {"x":0,"y":42,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "7.png": - { - "frame": {"x":0,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "8.png": - { - "frame": {"x":0,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "9.png": - { - "frame": {"x":0,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }}, - "meta": { - "app": "http://www.texturepacker.com", - "version": "1.0", - "image": "hud.png", - "format": "RGBA8888", - "size": {"w":64,"h":64}, - "scale": "1", - "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" - } - }; - /* eslint-enable */ -} diff --git a/packages/mixin-app-loader/LICENSE b/packages/mixin-app-loader/LICENSE new file mode 100644 index 0000000..5d16b50 --- /dev/null +++ b/packages/mixin-app-loader/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2013-2017 Mathew Groves, Chad Engler + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/packages/mixin-app-loader/README.md b/packages/mixin-app-loader/README.md new file mode 100644 index 0000000..3e89f86 --- /dev/null +++ b/packages/mixin-app-loader/README.md @@ -0,0 +1,13 @@ +# @pixi/mixin-app-loader + +## Installation + +```bash +npm install @pixi/mixin-app-loader +``` + +## Usage + +```js +import '@pixi/mixin-app-loader'; +``` \ No newline at end of file diff --git a/packages/mixin-app-loader/package.json b/packages/mixin-app-loader/package.json new file mode 100644 index 0000000..869ed4e --- /dev/null +++ b/packages/mixin-app-loader/package.json @@ -0,0 +1,41 @@ +{ + "name": "@pixi/mixin-app-loader", + "version": "5.0.0-alpha", + "main": "lib/mixin-app-loader.js", + "module": "lib/mixin-app-loader.es.js", + "description": "Support for loader in Application", + "author": "Mat Groves", + "contributors": [ + "Matt Karl " + ], + "homepage": "http://pixijs.com/", + "bugs": "https://github.com/pixijs/pixi.js/issues", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/pixijs/pixi.js.git" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "build:dev": "rollup -c", + "build": "rollup -cp", + "watch": "rollup -cw", + "postversion": "npm run build", + "test": "tester" + }, + "files": [ + "lib" + ], + "dependencies": { + "@pixi/app": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" + }, + "devDependencies": { + "@pixi/utils": "^5.0.0-alpha", + "@internal/tester": "^5.0.0-alpha", + "@internal/builder": "^5.0.0-alpha", + "rollup": "^0.50.0" + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/rollup.config.js b/packages/mixin-app-loader/rollup.config.js new file mode 100644 index 0000000..a73c874 --- /dev/null +++ b/packages/mixin-app-loader/rollup.config.js @@ -0,0 +1 @@ +export { default } from '@internal/builder'; diff --git a/packages/mixin-app-loader/src/index.js b/packages/mixin-app-loader/src/index.js new file mode 100644 index 0000000..354997a --- /dev/null +++ b/packages/mixin-app-loader/src/index.js @@ -0,0 +1,38 @@ +import { Application } from '@pixi/app'; +import { Loader, shared } from '@pixi/loaders'; + +Application.prototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperties(Application.prototype, { + loader: { + get() + { + if (!this._loader && this._options) + { + const { sharedLoader } = this._options; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +Application.prototype._parentDestroy = Application.prototype.destroy; +Application.prototype.destroy = function destroy(removeView) +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(removeView); +}; diff --git a/packages/mixin-app-loader/test/.eslintrc.json b/packages/mixin-app-loader/test/.eslintrc.json new file mode 100644 index 0000000..2094b04 --- /dev/null +++ b/packages/mixin-app-loader/test/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "globals": { + "expect": false, + "assert": false, + "sinon": false, + "PIXI": false + }, + "rules": { + "func-names": 0, + "no-unused-expressions": 0 + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/test/index.js b/packages/mixin-app-loader/test/index.js new file mode 100644 index 0000000..3468fe5 --- /dev/null +++ b/packages/mixin-app-loader/test/index.js @@ -0,0 +1,35 @@ +const { Application } = require('@pixi/app'); +const { Loader, shared } = require('@pixi/loaders'); +const { skipHello } = require('@pixi/utils'); + +require('../'); + +skipHello(); + +describe('PIXI.Application#loader', function () +{ + it('should contain loader property', function () + { + const obj = new Application(); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); + + it('should use sharedLoader option', function () + { + const obj = new Application({ sharedLoader: true }); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + expect(obj.loader).to.equal(shared); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); +}); diff --git a/packages/spritesheet/package.json b/packages/spritesheet/package.json index 34749e2..5f35442 100644 --- a/packages/spritesheet/package.json +++ b/packages/spritesheet/package.json @@ -31,7 +31,8 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/math": "^5.0.0-alpha", - "@pixi/utils": "^5.0.0-alpha" + "@pixi/utils": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" }, "devDependencies": { "@internal/builder": "^5.0.0-alpha", diff --git a/packages/spritesheet/src/SpritesheetLoader.js b/packages/spritesheet/src/SpritesheetLoader.js new file mode 100644 index 0000000..c7b34ea --- /dev/null +++ b/packages/spritesheet/src/SpritesheetLoader.js @@ -0,0 +1,83 @@ +import url from 'url'; +import { Resource, Loader } from '@pixi/loaders'; +import Spritesheet from './Spritesheet'; + +/** + * {@link PIXI.loaders.Loader Loader} middleware for loading + * texture atlases that have been created with TexturePacker or + * similar JSON-based spritesheet. This automatically generates + * Texture resources. + * @class + * @memberof PIXI + */ +export default class SpritesheetLoader +{ + /** + * Middleware function to support Spritesheets, this is automatically installed. + * @see PIXI.loaders.Loader.useMiddleware + */ + static middleware() + { + return function spritesheetLoader(resource, next) + { + const imageResourceName = `${resource.name}_image`; + + // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists + if (!resource.data + || resource.type !== Resource.TYPE.JSON + || !resource.data.frames + || this.resources[imageResourceName] + ) + { + next(); + + return; + } + + const loadOptions = { + crossOrigin: resource.crossOrigin, + loadType: Resource.LOAD_TYPE.IMAGE, + metadata: resource.metadata.imageMetadata, + parentResource: resource, + }; + + const resourcePath = SpritesheetLoader.getResourcePath(resource, this.baseUrl); + + // load the image for this sheet + this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) + { + const spritesheet = new Spritesheet( + res.texture.baseTexture, + resource.data, + resource.url + ); + + spritesheet.parse(() => + { + resource.spritesheet = spritesheet; + resource.textures = spritesheet.textures; + next(); + }); + }); + }; + } + + /** + * Get the spritesheets root path + * @param {PIXI.loader.Resource} resource - Resource to check path + * @param {string} baseUrl - Base root url + */ + static getResourcePath(resource, baseUrl) + { + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); + } +} + +// Install loader support for Spritesheet objects +Loader.useMiddleware(SpritesheetLoader.middleware); diff --git a/packages/spritesheet/src/index.js b/packages/spritesheet/src/index.js index 51d96a1..b9ee1e8 100644 --- a/packages/spritesheet/src/index.js +++ b/packages/spritesheet/src/index.js @@ -1 +1,2 @@ export { default as Spritesheet } from './Spritesheet'; +export { default as SpritesheetLoader } from './SpritesheetLoader'; diff --git a/packages/spritesheet/test/Spritesheet.js b/packages/spritesheet/test/Spritesheet.js new file mode 100644 index 0000000..35d8688 --- /dev/null +++ b/packages/spritesheet/test/Spritesheet.js @@ -0,0 +1,105 @@ +const { Spritesheet } = require('../'); +const { BaseTexture, Texture } = require('@pixi/core'); +const path = require('path'); + +describe('PIXI.Spritesheet', function () +{ + before(function () + { + this.resources = path.join(__dirname, 'resources'); + this.validate = function (spritesheet, done) + { + spritesheet.parse(function (textures) + { + const id = 'goldmine_10_5.png'; + const width = Math.floor(spritesheet.data.frames[id].frame.w); + const height = Math.floor(spritesheet.data.frames[id].frame.h); + + expect(Object.keys(textures).length).to.equal(1); + expect(Object.keys(spritesheet.textures).length).to.equal(1); + expect(textures[id]).to.be.an.instanceof(Texture); + expect(textures[id].width).to.equal(width / spritesheet.resolution); + expect(textures[id].height).to.equal(height / spritesheet.resolution); + expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); + spritesheet.destroy(true); + expect(spritesheet.textures).to.be.null; + expect(spritesheet.baseTexture).to.be.null; + done(); + }); + }; + }); + + it('should exist on PIXI', function () + { + expect(Spritesheet).to.be.a.function; + expect(Spritesheet.BATCH_SIZE).to.be.a.number; + }); + + it('should create an instance', function () + { + const baseTexture = new BaseTexture(); + const data = { + frames: {}, + meta: {}, + }; + + const spritesheet = new Spritesheet(baseTexture, data); + + expect(spritesheet.data).to.equal(data); + expect(spritesheet.baseTexture).to.equal(baseTexture); + expect(spritesheet.resolution).to.equal(1); + + spritesheet.destroy(true); + }); + + it('should create instance with scale resolution', function (done) + { + const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require + const image = new Image(); + + image.src = path.join(this.resources, data.meta.image); + image.onload = () => + { + const baseTexture = new BaseTexture(image, null, 1); + const spritesheet = new Spritesheet(baseTexture, data); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1.png'); + expect(spritesheet.resolution).to.equal(0.5); + this.validate(spritesheet, done); + }; + }); + + it('should create instance with BaseTexture source scale', function (done) + { + const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require + const baseTexture = new BaseTexture.fromImage(data.meta.image, undefined, undefined, 1.5); + const spritesheet = new Spritesheet(baseTexture, data); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1.png'); + expect(spritesheet.resolution).to.equal(0.5); + + this.validate(spritesheet, done); + }); + + it('should create instance with filename resolution', function (done) + { + const uri = path.resolve(this.resources, 'building1@2x.json'); + const data = require(uri); // eslint-disable-line global-require + const image = new Image(); + + image.src = path.join(this.resources, data.meta.image); + image.onload = () => + { + const baseTexture = new BaseTexture(image, null, 1); + const spritesheet = new Spritesheet(baseTexture, data, uri); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1@2x.png'); + expect(spritesheet.resolution).to.equal(2); + + this.validate(spritesheet, done); + }; + }); +}); diff --git a/packages/spritesheet/test/SpritesheetLoader.js b/packages/spritesheet/test/SpritesheetLoader.js new file mode 100644 index 0000000..e4205ec --- /dev/null +++ b/packages/spritesheet/test/SpritesheetLoader.js @@ -0,0 +1,214 @@ +const path = require('path'); +const { Loader, Resource } = require('@pixi/loaders'); +const { Texture, BaseTexture } = require('@pixi/core'); +const { SpritesheetLoader } = require('../'); + +describe('PIXI.SpritesheetLoader', function () +{ + it('should exist and return a function', function () + { + expect(SpritesheetLoader.middleware).to.be.a('function'); + expect(SpritesheetLoader.middleware()).to.be.a('function'); + }); + + it('should do nothing if the resource is not JSON', function () + { + const spy = sinon.spy(); + const res = {}; + + SpritesheetLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should do nothing if the resource is JSON, but improper format', function () + { + const spy = sinon.spy(); + const res = createMockResource(Resource.TYPE.JSON, {}); + + SpritesheetLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should load the image & create textures if json is properly formatted', function () + { + const spy = sinon.spy(); + const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); + const loader = new Loader(); + const addStub = sinon.stub(loader, 'add'); + const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); + + imgRes.texture = new Texture(new BaseTexture(imgRes.data)); + + addStub.yields(imgRes); + + SpritesheetLoader.middleware().call(loader, res, spy); + + addStub.restore(); + + expect(spy).to.have.been.calledOnce; + expect(addStub).to.have.been.calledWith( + `${res.name}_image`, + `${path.dirname(res.url)}/${res.data.meta.image}` + ); + expect(res).to.have.property('textures') + .that.is.an('object') + .with.keys(Object.keys(getJsonSpritesheet().frames)) + .and.has.property('0.png') + .that.is.an.instanceof(Texture); + }); + + it('should build the image url', function () + { + function getPath(url, image) + { + return SpritesheetLoader.getResourcePath({ + url, + data: { meta: { image } }, + }); + } + + let result = getPath('http://some.com/spritesheet.json', 'img.png'); + + expect(result).to.be.equals('http://some.com/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); + expect(result).to.be.equals('http://some.com/some/dir/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); + expect(result).to.be.equals('http://some.com/some/dir/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); + expect(result).to.be.equals('http://some.com/some/img.png'); + + result = getPath('/spritesheet.json', 'img.png'); + expect(result).to.be.equals('/img.png'); + + result = getPath('/some/dir/spritesheet.json', 'img.png'); + expect(result).to.be.equals('/some/dir/img.png'); + + result = getPath('/some/dir/spritesheet.json', './img.png'); + expect(result).to.be.equals('/some/dir/img.png'); + + result = getPath('/some/dir/spritesheet.json', '../img.png'); + expect(result).to.be.equals('/some/img.png'); + }); + + // TODO: Test that rectangles are created correctly. + // TODO: Test that bathc processing works correctly. + // TODO: Test that resolution processing works correctly. + // TODO: Test that metadata is honored. +}); + +function createMockResource(type, data) +{ + const name = `${Math.floor(Date.now() * Math.random())}`; + + return { + url: `http://localhost/doesnt_exist/${name}`, + name, + type, + data, + metadata: {}, + }; +} + +function getJsonSpritesheet() +{ + /* eslint-disable */ + return {"frames": { + "0.png": + { + "frame": {"x":14,"y":28,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "1.png": + { + "frame": {"x":14,"y":42,"w":12,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, + "sourceSize": {"w":12,"h":14} + }, + "2.png": + { + "frame": {"x":14,"y":14,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "3.png": + { + "frame": {"x":42,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "4.png": + { + "frame": {"x":28,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "5.png": + { + "frame": {"x":14,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "6.png": + { + "frame": {"x":0,"y":42,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "7.png": + { + "frame": {"x":0,"y":28,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "8.png": + { + "frame": {"x":0,"y":14,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "9.png": + { + "frame": {"x":0,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }}, + "meta": { + "app": "http://www.texturepacker.com", + "version": "1.0", + "image": "hud.png", + "format": "RGBA8888", + "size": {"w":64,"h":64}, + "scale": "1", + "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" + } + }; + /* eslint-enable */ +} diff --git a/packages/spritesheet/test/index.js b/packages/spritesheet/test/index.js index 35d8688..8cf5636 100644 --- a/packages/spritesheet/test/index.js +++ b/packages/spritesheet/test/index.js @@ -1,105 +1,2 @@ -const { Spritesheet } = require('../'); -const { BaseTexture, Texture } = require('@pixi/core'); -const path = require('path'); - -describe('PIXI.Spritesheet', function () -{ - before(function () - { - this.resources = path.join(__dirname, 'resources'); - this.validate = function (spritesheet, done) - { - spritesheet.parse(function (textures) - { - const id = 'goldmine_10_5.png'; - const width = Math.floor(spritesheet.data.frames[id].frame.w); - const height = Math.floor(spritesheet.data.frames[id].frame.h); - - expect(Object.keys(textures).length).to.equal(1); - expect(Object.keys(spritesheet.textures).length).to.equal(1); - expect(textures[id]).to.be.an.instanceof(Texture); - expect(textures[id].width).to.equal(width / spritesheet.resolution); - expect(textures[id].height).to.equal(height / spritesheet.resolution); - expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); - spritesheet.destroy(true); - expect(spritesheet.textures).to.be.null; - expect(spritesheet.baseTexture).to.be.null; - done(); - }); - }; - }); - - it('should exist on PIXI', function () - { - expect(Spritesheet).to.be.a.function; - expect(Spritesheet.BATCH_SIZE).to.be.a.number; - }); - - it('should create an instance', function () - { - const baseTexture = new BaseTexture(); - const data = { - frames: {}, - meta: {}, - }; - - const spritesheet = new Spritesheet(baseTexture, data); - - expect(spritesheet.data).to.equal(data); - expect(spritesheet.baseTexture).to.equal(baseTexture); - expect(spritesheet.resolution).to.equal(1); - - spritesheet.destroy(true); - }); - - it('should create instance with scale resolution', function (done) - { - const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require - const image = new Image(); - - image.src = path.join(this.resources, data.meta.image); - image.onload = () => - { - const baseTexture = new BaseTexture(image, null, 1); - const spritesheet = new Spritesheet(baseTexture, data); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1.png'); - expect(spritesheet.resolution).to.equal(0.5); - this.validate(spritesheet, done); - }; - }); - - it('should create instance with BaseTexture source scale', function (done) - { - const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require - const baseTexture = new BaseTexture.fromImage(data.meta.image, undefined, undefined, 1.5); - const spritesheet = new Spritesheet(baseTexture, data); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1.png'); - expect(spritesheet.resolution).to.equal(0.5); - - this.validate(spritesheet, done); - }); - - it('should create instance with filename resolution', function (done) - { - const uri = path.resolve(this.resources, 'building1@2x.json'); - const data = require(uri); // eslint-disable-line global-require - const image = new Image(); - - image.src = path.join(this.resources, data.meta.image); - image.onload = () => - { - const baseTexture = new BaseTexture(image, null, 1); - const spritesheet = new Spritesheet(baseTexture, data, uri); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1@2x.png'); - expect(spritesheet.resolution).to.equal(2); - - this.validate(spritesheet, done); - }; - }); -}); +require('./Spritesheet'); +require('./SpritesheetLoader'); diff --git a/packages/text-bitmap/package.json b/packages/text-bitmap/package.json index a8a5f95..4b47da6 100644 --- a/packages/text-bitmap/package.json +++ b/packages/text-bitmap/package.json @@ -23,7 +23,7 @@ "build": "rollup -cp", "watch": "rollup -cw", "postversion": "npm run build", - "test": "exit 0" + "test": "tester" }, "files": [ "lib" @@ -31,12 +31,15 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha", "@pixi/sprite": "^5.0.0-alpha", "@pixi/settings": "^5.0.0-alpha", "@pixi/math": "^5.0.0-alpha", "@pixi/display": "^5.0.0-alpha" }, "devDependencies": { + "@pixi/spritesheet": "^5.0.0-alpha", + "@internal/tester": "^5.0.0-alpha", "@internal/builder": "^5.0.0-alpha", "rollup": "^0.50.0" } diff --git a/packages/text-bitmap/src/BitmapFontLoader.js b/packages/text-bitmap/src/BitmapFontLoader.js new file mode 100644 index 0000000..3956060 --- /dev/null +++ b/packages/text-bitmap/src/BitmapFontLoader.js @@ -0,0 +1,112 @@ +import * as path from 'path'; +import { TextureCache } from '@pixi/utils'; +import { Resource, Loader } from '@pixi/loaders'; +import BitmapText from './BitmapText'; + +/** + * {@link PIXI.loaders.Loader Loader} middleware for loading + * bitmap-based fonts suitable for using with {@link PIXI.BitmapText}. + * @class + * @memberof PIXI + */ +export default class BitmapFontLoader +{ + /** + * Register a BitmapText font from loader resource. + * + * @param {PIXI.loaders.Resource} resource - Loader resource. + * @param {PIXI.Texture} texture - Reference to texture. + */ + static parse(resource, texture) + { + resource.bitmapFont = BitmapText.registerFont(resource.data, texture); + } + + /** + * Middleware function to support BitmapText fonts, this is automatically installed. + * @see PIXI.loaders.Loader.useMiddleware + */ + static middleware() + { + return function bitmapFontLoader(resource, next) + { + // skip if no data or not xml data + if (!resource.data || resource.type !== Resource.TYPE.XML) + { + next(); + + return; + } + + // skip if not bitmap font data, using some silly duck-typing + if (resource.data.getElementsByTagName('page').length === 0 + || resource.data.getElementsByTagName('info').length === 0 + || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null + ) + { + next(); + + return; + } + + let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; + + if (resource.isDataUrl) + { + if (xmlUrl === '.') + { + xmlUrl = ''; + } + + if (this.baseUrl && xmlUrl) + { + // if baseurl has a trailing slash then add one to xmlUrl so the replace works below + if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') + { + xmlUrl += '/'; + } + } + } + + // remove baseUrl from xmlUrl + xmlUrl = xmlUrl.replace(this.baseUrl, ''); + + // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. + if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') + { + xmlUrl += '/'; + } + + const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); + + if (TextureCache[textureUrl]) + { + // reuse existing texture + BitmapFontLoader.parse(resource, TextureCache[textureUrl]); + next(); + } + else + { + const loadOptions = { + crossOrigin: resource.crossOrigin, + loadType: Resource.LOAD_TYPE.IMAGE, + metadata: resource.metadata.imageMetadata, + parentResource: resource, + }; + + // load the texture for the font + this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => + { + BitmapFontLoader.parse(resource, res.texture); + next(); + }); + } + }; + } +} + +// Add custom add support for loading fnt files as XML +Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); + +// Install loader support for BitmapText +Loader.useMiddleware(BitmapFontLoader.middleware); diff --git a/packages/text-bitmap/src/index.js b/packages/text-bitmap/src/index.js index a98e7b4..e5afd2c 100644 --- a/packages/text-bitmap/src/index.js +++ b/packages/text-bitmap/src/index.js @@ -1 +1,2 @@ export { default as BitmapText } from './BitmapText'; +export { default as BitmapFontLoader } from './BitmapFontLoader'; diff --git a/packages/text-bitmap/test/.eslintrc.json b/packages/text-bitmap/test/.eslintrc.json new file mode 100644 index 0000000..2094b04 --- /dev/null +++ b/packages/text-bitmap/test/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "globals": { + "expect": false, + "assert": false, + "sinon": false, + "PIXI": false + }, + "rules": { + "func-names": 0, + "no-unused-expressions": 0 + } +} \ No newline at end of file diff --git a/packages/text-bitmap/test/BitmapFontLoader.js b/packages/text-bitmap/test/BitmapFontLoader.js new file mode 100644 index 0000000..a76fc1c --- /dev/null +++ b/packages/text-bitmap/test/BitmapFontLoader.js @@ -0,0 +1,319 @@ +const path = require('path'); +const fs = require('fs'); +const { BaseTextureCache, TextureCache } = require('@pixi/utils'); +const { Texture, BaseTexture } = require('@pixi/core'); +const { Spritesheet } = require('@pixi/spritesheet'); +const { BitmapText, BitmapFontLoader } = require('../'); + +describe('PIXI.BitmapFontLoader', function () +{ + afterEach(function () + { + for (const font in BitmapText.fonts) + { + delete BitmapText.fonts[font]; + } + for (const baseTexture in BaseTextureCache) + { + delete BaseTextureCache[baseTexture]; + } + for (const texture in TextureCache) + { + delete TextureCache[texture]; + } + }); + + before(function (done) + { + const resolveURL = (url) => path.resolve(this.resources, url); + + this.resources = path.join(__dirname, 'resources'); + this.fontXML = null; + this.fontScaledXML = null; + this.fontImage = null; + this.fontScaledImage = null; + this.atlasImage = null; + this.atlasScaledImage = null; + this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require + this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require + + const loadXML = (url) => new Promise((resolve) => + fs.readFile(resolveURL(url), 'utf8', (err, data) => + { + expect(err).to.be.null; + resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); + })); + + const loadImage = (url) => new Promise((resolve) => + { + const image = new Image(); + + image.onload = () => resolve(image); + image.src = resolveURL(url); + }); + + Promise.all([ + loadXML('font.fnt'), + loadXML('font@0.5x.fnt'), + loadImage('font.png'), + loadImage('font@0.5x.png'), + loadImage('atlas.png'), + loadImage('atlas@0.5x.png'), + ]).then(([ + fontXML, + fontScaledXML, + fontImage, + fontScaledImage, + atlasImage, + atlasScaledImage, + ]) => + { + this.fontXML = fontXML; + this.fontScaledXML = fontScaledXML; + this.fontImage = fontImage; + this.fontScaledImage = fontScaledImage; + this.atlasImage = atlasImage; + this.atlasScaledImage = atlasScaledImage; + done(); + }); + }); + + it('should exist and return a function', function () + { + expect(BitmapFontLoader.middleware).to.be.a('function'); + expect(BitmapFontLoader.middleware()).to.be.a('function'); + }); + + it('should do nothing if the resource is not XML', function () + { + const spy = sinon.spy(); + const res = {}; + + BitmapFontLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should do nothing if the resource is not properly formatted XML', function () + { + const spy = sinon.spy(); + const res = { data: document.createDocumentFragment() }; + + BitmapFontLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + // TODO: Test the texture cache code path. + // TODO: Test the loading texture code path. + // TODO: Test data-url code paths. + + it('should properly register bitmap font', function (done) + { + const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); + 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charA.texture.frame.x).to.equal(2); + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charB.texture.frame.x).to.equal(2); + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charC.texture.frame.x).to.equal(23); + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charD.texture.frame.x).to.equal(19); + 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]; + + expect(charE).to.be.undefined; + done(); + }); + + it('should properly register SCALED bitmap font', function (done) + { + const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); + const font = BitmapText.registerFont(this.fontScaledXML, 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 + 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]; + + expect(charE).to.be.undefined; + done(); + }); + + it('should properly register bitmap font NESTED into spritesheet', function (done) + { + const baseTexture = new BaseTexture(this.atlasImage, null, 1); + const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); + + spritesheet.parse(() => + { + const fontTexture = Texture.fromFrame('resources/font.png'); + const font = BitmapText.registerFont(this.fontXML, fontTexture); + const fontX = 158; // bare value from spritesheet frame + const fontY = 2; // bare value from spritesheet frame + + 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charA.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charB.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charC.texture.frame.x).to.equal(fontX + 23); + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charD.texture.frame.x).to.equal(fontX + 19); + 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]; + + expect(charE).to.be.undefined; + done(); + }); + }); + + it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) + { + const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); + const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); + + spritesheet.resolution = 1; + + spritesheet.parse(() => + { + const fontTexture = Texture.fromFrame('resources/font.png'); + const font = BitmapText.registerFont(this.fontXML, fontTexture); + const fontX = 158; // bare value from spritesheet frame + const fontY = 2; // bare value from spritesheet frame + + 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charA.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charB.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charC.texture.frame.x).to.equal(fontX + 23); + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charD.texture.frame.x).to.equal(fontX + 19); + 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]; + + expect(charE).to.be.undefined; + done(); + }); + }); + + it('should parse exist', function () + { + expect(BitmapFontLoader.parse).to.be.a('function'); + }); +}); diff --git a/packages/text-bitmap/test/index.js b/packages/text-bitmap/test/index.js new file mode 100644 index 0000000..688fee2 --- /dev/null +++ b/packages/text-bitmap/test/index.js @@ -0,0 +1 @@ +require('./BitmapFontLoader'); diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/packages/loaders/src/spritesheetParser.js b/packages/loaders/src/spritesheetParser.js deleted file mode 100644 index f7fa206..0000000 --- a/packages/loaders/src/spritesheetParser.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Resource } from 'resource-loader'; -import url from 'url'; -import { Spritesheet } from '@pixi/spritesheet'; - -export default function () -{ - return function spritesheetParser(resource, next) - { - const imageResourceName = `${resource.name}_image`; - - // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists - if (!resource.data - || resource.type !== Resource.TYPE.JSON - || !resource.data.frames - || this.resources[imageResourceName] - ) - { - next(); - - return; - } - - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - const resourcePath = getResourcePath(resource, this.baseUrl); - - // load the image for this sheet - this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) - { - const spritesheet = new Spritesheet( - res.texture.baseTexture, - resource.data, - resource.url - ); - - spritesheet.parse(() => - { - resource.spritesheet = spritesheet; - resource.textures = spritesheet.textures; - next(); - }); - }); - }; -} - -export function getResourcePath(resource, baseUrl) -{ - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - return resource.data.meta.image; - } - - return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); -} diff --git a/packages/loaders/test/bitmapFontParser.js b/packages/loaders/test/bitmapFontParser.js deleted file mode 100644 index 43ef194..0000000 --- a/packages/loaders/test/bitmapFontParser.js +++ /dev/null @@ -1,325 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const { BitmapText } = require('@pixi/text-bitmap'); -const { BaseTextureCache, TextureCache } = require('@pixi/utils'); -const { Texture, BaseTexture } = require('@pixi/core'); -const { Spritesheet } = require('@pixi/spritesheet'); -const { bitmapFontParser, parseBitmapFontData } = require('../'); - -describe('PIXI.loaders.bitmapFontParser', function () -{ - afterEach(function () - { - for (const font in BitmapText.fonts) - { - delete BitmapText.fonts[font]; - } - for (const baseTexture in BaseTextureCache) - { - delete BaseTextureCache[baseTexture]; - } - for (const texture in TextureCache) - { - delete TextureCache[texture]; - } - }); - - before(function (done) - { - const resolveURL = (url) => path.resolve(this.resources, url); - - this.resources = path.join(__dirname, 'resources'); - this.fontXML = null; - this.fontScaledXML = null; - this.fontImage = null; - this.fontScaledImage = null; - this.atlasImage = null; - this.atlasScaledImage = null; - this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require - this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require - - const loadXML = (url) => new Promise((resolve) => - fs.readFile(resolveURL(url), 'utf8', (err, data) => - { - expect(err).to.be.null; - resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); - })); - - const loadImage = (url) => new Promise((resolve) => - { - const image = new Image(); - - image.onload = () => resolve(image); - image.src = resolveURL(url); - }); - - Promise.all([ - loadXML('font.fnt'), - loadXML('font@0.5x.fnt'), - loadImage('font.png'), - loadImage('font@0.5x.png'), - loadImage('atlas.png'), - loadImage('atlas@0.5x.png'), - ]).then(([ - fontXML, - fontScaledXML, - fontImage, - fontScaledImage, - atlasImage, - atlasScaledImage, - ]) => - { - this.fontXML = fontXML; - this.fontScaledXML = fontScaledXML; - this.fontImage = fontImage; - this.fontScaledImage = fontScaledImage; - this.atlasImage = atlasImage; - this.atlasScaledImage = atlasScaledImage; - done(); - }); - }); - - it('should exist and return a function', function () - { - expect(bitmapFontParser).to.be.a('function'); - expect(bitmapFontParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not XML', function () - { - const spy = sinon.spy(); - const res = {}; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is not properly formatted XML', function () - { - const spy = sinon.spy(); - const res = { data: document.createDocumentFragment() }; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - // TODO: Test the texture cache code path. - // TODO: Test the loading texture code path. - // TODO: Test data-url code paths. - - it('should properly register bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charA.texture.frame.x).to.equal(2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charB.texture.frame.x).to.equal(2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charC.texture.frame.x).to.equal(23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charD.texture.frame.x).to.equal(19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register SCALED bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); - const font = BitmapText.registerFont(this.fontScaledXML, 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register bitmap font NESTED into spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); - - it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); - - spritesheet.resolution = 1; - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); -}); - -describe('PIXI.loaders.parseBitmapFontData', function () -{ - it('should exist', function () - { - expect(parseBitmapFontData).to.be.a('function'); - }); - - // TODO: Test the parser code. -}); diff --git a/packages/loaders/test/index.js b/packages/loaders/test/index.js index f00de5f..72c6554 100644 --- a/packages/loaders/test/index.js +++ b/packages/loaders/test/index.js @@ -1,4 +1,2 @@ -require('./bitmapFontParser'); require('./loader'); -require('./spritesheetParser'); require('./textureParser'); diff --git a/packages/loaders/test/resources/atlas.json b/packages/loaders/test/resources/atlas.json deleted file mode 100644 index 86e65a5..0000000 --- a/packages/loaders/test/resources/atlas.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas.png b/packages/loaders/test/resources/atlas.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/atlas@0.5x.json b/packages/loaders/test/resources/atlas@0.5x.json deleted file mode 100644 index ae990a1..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas@0.5x.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas@0.5x.png b/packages/loaders/test/resources/atlas@0.5x.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font.fnt b/packages/loaders/test/resources/font.fnt deleted file mode 100644 index 56e1060..0000000 --- a/packages/loaders/test/resources/font.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font.png b/packages/loaders/test/resources/font.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font@0.5x.fnt b/packages/loaders/test/resources/font@0.5x.fnt deleted file mode 100644 index 6c247c7..0000000 --- a/packages/loaders/test/resources/font@0.5x.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font@0.5x.png b/packages/loaders/test/resources/font@0.5x.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/spritesheetParser.js b/packages/loaders/test/spritesheetParser.js deleted file mode 100644 index 98890b8..0000000 --- a/packages/loaders/test/spritesheetParser.js +++ /dev/null @@ -1,213 +0,0 @@ -const path = require('path'); -const { spritesheetParser, Resource, Loader, getResourcePath } = require('../'); -const { Texture, BaseTexture } = require('@pixi/core'); - -describe('PIXI.loaders.spritesheetParser', function () -{ - it('should exist and return a function', function () - { - expect(spritesheetParser).to.be.a('function'); - expect(spritesheetParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not JSON', function () - { - const spy = sinon.spy(); - const res = {}; - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is JSON, but improper format', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, {}); - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should load the image & create textures if json is properly formatted', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); - const loader = new Loader(); - const addStub = sinon.stub(loader, 'add'); - const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); - - imgRes.texture = new Texture(new BaseTexture(imgRes.data)); - - addStub.yields(imgRes); - - spritesheetParser().call(loader, res, spy); - - addStub.restore(); - - expect(spy).to.have.been.calledOnce; - expect(addStub).to.have.been.calledWith( - `${res.name}_image`, - `${path.dirname(res.url)}/${res.data.meta.image}` - ); - expect(res).to.have.property('textures') - .that.is.an('object') - .with.keys(Object.keys(getJsonSpritesheet().frames)) - .and.has.property('0.png') - .that.is.an.instanceof(Texture); - }); - - it('should build the image url', function () - { - function getPath(url, image) - { - return getResourcePath({ - url, - data: { meta: { image } }, - }); - } - - let result = getPath('http://some.com/spritesheet.json', 'img.png'); - - expect(result).to.be.equals('http://some.com/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('http://some.com/some/img.png'); - - result = getPath('/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/img.png'); - - result = getPath('/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('/some/img.png'); - }); - - // TODO: Test that rectangles are created correctly. - // TODO: Test that bathc processing works correctly. - // TODO: Test that resolution processing works correctly. - // TODO: Test that metadata is honored. -}); - -function createMockResource(type, data) -{ - const name = `${Math.floor(Date.now() * Math.random())}`; - - return { - url: `http://localhost/doesnt_exist/${name}`, - name, - type, - data, - metadata: {}, - }; -} - -function getJsonSpritesheet() -{ - /* eslint-disable */ - return {"frames": { - "0.png": - { - "frame": {"x":14,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "1.png": - { - "frame": {"x":14,"y":42,"w":12,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, - "sourceSize": {"w":12,"h":14} - }, - "2.png": - { - "frame": {"x":14,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "3.png": - { - "frame": {"x":42,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "4.png": - { - "frame": {"x":28,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "5.png": - { - "frame": {"x":14,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "6.png": - { - "frame": {"x":0,"y":42,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "7.png": - { - "frame": {"x":0,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "8.png": - { - "frame": {"x":0,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "9.png": - { - "frame": {"x":0,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }}, - "meta": { - "app": "http://www.texturepacker.com", - "version": "1.0", - "image": "hud.png", - "format": "RGBA8888", - "size": {"w":64,"h":64}, - "scale": "1", - "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" - } - }; - /* eslint-enable */ -} diff --git a/packages/mixin-app-loader/LICENSE b/packages/mixin-app-loader/LICENSE new file mode 100644 index 0000000..5d16b50 --- /dev/null +++ b/packages/mixin-app-loader/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2013-2017 Mathew Groves, Chad Engler + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/packages/mixin-app-loader/README.md b/packages/mixin-app-loader/README.md new file mode 100644 index 0000000..3e89f86 --- /dev/null +++ b/packages/mixin-app-loader/README.md @@ -0,0 +1,13 @@ +# @pixi/mixin-app-loader + +## Installation + +```bash +npm install @pixi/mixin-app-loader +``` + +## Usage + +```js +import '@pixi/mixin-app-loader'; +``` \ No newline at end of file diff --git a/packages/mixin-app-loader/package.json b/packages/mixin-app-loader/package.json new file mode 100644 index 0000000..869ed4e --- /dev/null +++ b/packages/mixin-app-loader/package.json @@ -0,0 +1,41 @@ +{ + "name": "@pixi/mixin-app-loader", + "version": "5.0.0-alpha", + "main": "lib/mixin-app-loader.js", + "module": "lib/mixin-app-loader.es.js", + "description": "Support for loader in Application", + "author": "Mat Groves", + "contributors": [ + "Matt Karl " + ], + "homepage": "http://pixijs.com/", + "bugs": "https://github.com/pixijs/pixi.js/issues", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/pixijs/pixi.js.git" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "build:dev": "rollup -c", + "build": "rollup -cp", + "watch": "rollup -cw", + "postversion": "npm run build", + "test": "tester" + }, + "files": [ + "lib" + ], + "dependencies": { + "@pixi/app": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" + }, + "devDependencies": { + "@pixi/utils": "^5.0.0-alpha", + "@internal/tester": "^5.0.0-alpha", + "@internal/builder": "^5.0.0-alpha", + "rollup": "^0.50.0" + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/rollup.config.js b/packages/mixin-app-loader/rollup.config.js new file mode 100644 index 0000000..a73c874 --- /dev/null +++ b/packages/mixin-app-loader/rollup.config.js @@ -0,0 +1 @@ +export { default } from '@internal/builder'; diff --git a/packages/mixin-app-loader/src/index.js b/packages/mixin-app-loader/src/index.js new file mode 100644 index 0000000..354997a --- /dev/null +++ b/packages/mixin-app-loader/src/index.js @@ -0,0 +1,38 @@ +import { Application } from '@pixi/app'; +import { Loader, shared } from '@pixi/loaders'; + +Application.prototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperties(Application.prototype, { + loader: { + get() + { + if (!this._loader && this._options) + { + const { sharedLoader } = this._options; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +Application.prototype._parentDestroy = Application.prototype.destroy; +Application.prototype.destroy = function destroy(removeView) +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(removeView); +}; diff --git a/packages/mixin-app-loader/test/.eslintrc.json b/packages/mixin-app-loader/test/.eslintrc.json new file mode 100644 index 0000000..2094b04 --- /dev/null +++ b/packages/mixin-app-loader/test/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "globals": { + "expect": false, + "assert": false, + "sinon": false, + "PIXI": false + }, + "rules": { + "func-names": 0, + "no-unused-expressions": 0 + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/test/index.js b/packages/mixin-app-loader/test/index.js new file mode 100644 index 0000000..3468fe5 --- /dev/null +++ b/packages/mixin-app-loader/test/index.js @@ -0,0 +1,35 @@ +const { Application } = require('@pixi/app'); +const { Loader, shared } = require('@pixi/loaders'); +const { skipHello } = require('@pixi/utils'); + +require('../'); + +skipHello(); + +describe('PIXI.Application#loader', function () +{ + it('should contain loader property', function () + { + const obj = new Application(); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); + + it('should use sharedLoader option', function () + { + const obj = new Application({ sharedLoader: true }); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + expect(obj.loader).to.equal(shared); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); +}); diff --git a/packages/spritesheet/package.json b/packages/spritesheet/package.json index 34749e2..5f35442 100644 --- a/packages/spritesheet/package.json +++ b/packages/spritesheet/package.json @@ -31,7 +31,8 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/math": "^5.0.0-alpha", - "@pixi/utils": "^5.0.0-alpha" + "@pixi/utils": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" }, "devDependencies": { "@internal/builder": "^5.0.0-alpha", diff --git a/packages/spritesheet/src/SpritesheetLoader.js b/packages/spritesheet/src/SpritesheetLoader.js new file mode 100644 index 0000000..c7b34ea --- /dev/null +++ b/packages/spritesheet/src/SpritesheetLoader.js @@ -0,0 +1,83 @@ +import url from 'url'; +import { Resource, Loader } from '@pixi/loaders'; +import Spritesheet from './Spritesheet'; + +/** + * {@link PIXI.loaders.Loader Loader} middleware for loading + * texture atlases that have been created with TexturePacker or + * similar JSON-based spritesheet. This automatically generates + * Texture resources. + * @class + * @memberof PIXI + */ +export default class SpritesheetLoader +{ + /** + * Middleware function to support Spritesheets, this is automatically installed. + * @see PIXI.loaders.Loader.useMiddleware + */ + static middleware() + { + return function spritesheetLoader(resource, next) + { + const imageResourceName = `${resource.name}_image`; + + // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists + if (!resource.data + || resource.type !== Resource.TYPE.JSON + || !resource.data.frames + || this.resources[imageResourceName] + ) + { + next(); + + return; + } + + const loadOptions = { + crossOrigin: resource.crossOrigin, + loadType: Resource.LOAD_TYPE.IMAGE, + metadata: resource.metadata.imageMetadata, + parentResource: resource, + }; + + const resourcePath = SpritesheetLoader.getResourcePath(resource, this.baseUrl); + + // load the image for this sheet + this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) + { + const spritesheet = new Spritesheet( + res.texture.baseTexture, + resource.data, + resource.url + ); + + spritesheet.parse(() => + { + resource.spritesheet = spritesheet; + resource.textures = spritesheet.textures; + next(); + }); + }); + }; + } + + /** + * Get the spritesheets root path + * @param {PIXI.loader.Resource} resource - Resource to check path + * @param {string} baseUrl - Base root url + */ + static getResourcePath(resource, baseUrl) + { + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); + } +} + +// Install loader support for Spritesheet objects +Loader.useMiddleware(SpritesheetLoader.middleware); diff --git a/packages/spritesheet/src/index.js b/packages/spritesheet/src/index.js index 51d96a1..b9ee1e8 100644 --- a/packages/spritesheet/src/index.js +++ b/packages/spritesheet/src/index.js @@ -1 +1,2 @@ export { default as Spritesheet } from './Spritesheet'; +export { default as SpritesheetLoader } from './SpritesheetLoader'; diff --git a/packages/spritesheet/test/Spritesheet.js b/packages/spritesheet/test/Spritesheet.js new file mode 100644 index 0000000..35d8688 --- /dev/null +++ b/packages/spritesheet/test/Spritesheet.js @@ -0,0 +1,105 @@ +const { Spritesheet } = require('../'); +const { BaseTexture, Texture } = require('@pixi/core'); +const path = require('path'); + +describe('PIXI.Spritesheet', function () +{ + before(function () + { + this.resources = path.join(__dirname, 'resources'); + this.validate = function (spritesheet, done) + { + spritesheet.parse(function (textures) + { + const id = 'goldmine_10_5.png'; + const width = Math.floor(spritesheet.data.frames[id].frame.w); + const height = Math.floor(spritesheet.data.frames[id].frame.h); + + expect(Object.keys(textures).length).to.equal(1); + expect(Object.keys(spritesheet.textures).length).to.equal(1); + expect(textures[id]).to.be.an.instanceof(Texture); + expect(textures[id].width).to.equal(width / spritesheet.resolution); + expect(textures[id].height).to.equal(height / spritesheet.resolution); + expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); + spritesheet.destroy(true); + expect(spritesheet.textures).to.be.null; + expect(spritesheet.baseTexture).to.be.null; + done(); + }); + }; + }); + + it('should exist on PIXI', function () + { + expect(Spritesheet).to.be.a.function; + expect(Spritesheet.BATCH_SIZE).to.be.a.number; + }); + + it('should create an instance', function () + { + const baseTexture = new BaseTexture(); + const data = { + frames: {}, + meta: {}, + }; + + const spritesheet = new Spritesheet(baseTexture, data); + + expect(spritesheet.data).to.equal(data); + expect(spritesheet.baseTexture).to.equal(baseTexture); + expect(spritesheet.resolution).to.equal(1); + + spritesheet.destroy(true); + }); + + it('should create instance with scale resolution', function (done) + { + const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require + const image = new Image(); + + image.src = path.join(this.resources, data.meta.image); + image.onload = () => + { + const baseTexture = new BaseTexture(image, null, 1); + const spritesheet = new Spritesheet(baseTexture, data); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1.png'); + expect(spritesheet.resolution).to.equal(0.5); + this.validate(spritesheet, done); + }; + }); + + it('should create instance with BaseTexture source scale', function (done) + { + const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require + const baseTexture = new BaseTexture.fromImage(data.meta.image, undefined, undefined, 1.5); + const spritesheet = new Spritesheet(baseTexture, data); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1.png'); + expect(spritesheet.resolution).to.equal(0.5); + + this.validate(spritesheet, done); + }); + + it('should create instance with filename resolution', function (done) + { + const uri = path.resolve(this.resources, 'building1@2x.json'); + const data = require(uri); // eslint-disable-line global-require + const image = new Image(); + + image.src = path.join(this.resources, data.meta.image); + image.onload = () => + { + const baseTexture = new BaseTexture(image, null, 1); + const spritesheet = new Spritesheet(baseTexture, data, uri); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1@2x.png'); + expect(spritesheet.resolution).to.equal(2); + + this.validate(spritesheet, done); + }; + }); +}); diff --git a/packages/spritesheet/test/SpritesheetLoader.js b/packages/spritesheet/test/SpritesheetLoader.js new file mode 100644 index 0000000..e4205ec --- /dev/null +++ b/packages/spritesheet/test/SpritesheetLoader.js @@ -0,0 +1,214 @@ +const path = require('path'); +const { Loader, Resource } = require('@pixi/loaders'); +const { Texture, BaseTexture } = require('@pixi/core'); +const { SpritesheetLoader } = require('../'); + +describe('PIXI.SpritesheetLoader', function () +{ + it('should exist and return a function', function () + { + expect(SpritesheetLoader.middleware).to.be.a('function'); + expect(SpritesheetLoader.middleware()).to.be.a('function'); + }); + + it('should do nothing if the resource is not JSON', function () + { + const spy = sinon.spy(); + const res = {}; + + SpritesheetLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should do nothing if the resource is JSON, but improper format', function () + { + const spy = sinon.spy(); + const res = createMockResource(Resource.TYPE.JSON, {}); + + SpritesheetLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should load the image & create textures if json is properly formatted', function () + { + const spy = sinon.spy(); + const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); + const loader = new Loader(); + const addStub = sinon.stub(loader, 'add'); + const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); + + imgRes.texture = new Texture(new BaseTexture(imgRes.data)); + + addStub.yields(imgRes); + + SpritesheetLoader.middleware().call(loader, res, spy); + + addStub.restore(); + + expect(spy).to.have.been.calledOnce; + expect(addStub).to.have.been.calledWith( + `${res.name}_image`, + `${path.dirname(res.url)}/${res.data.meta.image}` + ); + expect(res).to.have.property('textures') + .that.is.an('object') + .with.keys(Object.keys(getJsonSpritesheet().frames)) + .and.has.property('0.png') + .that.is.an.instanceof(Texture); + }); + + it('should build the image url', function () + { + function getPath(url, image) + { + return SpritesheetLoader.getResourcePath({ + url, + data: { meta: { image } }, + }); + } + + let result = getPath('http://some.com/spritesheet.json', 'img.png'); + + expect(result).to.be.equals('http://some.com/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); + expect(result).to.be.equals('http://some.com/some/dir/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); + expect(result).to.be.equals('http://some.com/some/dir/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); + expect(result).to.be.equals('http://some.com/some/img.png'); + + result = getPath('/spritesheet.json', 'img.png'); + expect(result).to.be.equals('/img.png'); + + result = getPath('/some/dir/spritesheet.json', 'img.png'); + expect(result).to.be.equals('/some/dir/img.png'); + + result = getPath('/some/dir/spritesheet.json', './img.png'); + expect(result).to.be.equals('/some/dir/img.png'); + + result = getPath('/some/dir/spritesheet.json', '../img.png'); + expect(result).to.be.equals('/some/img.png'); + }); + + // TODO: Test that rectangles are created correctly. + // TODO: Test that bathc processing works correctly. + // TODO: Test that resolution processing works correctly. + // TODO: Test that metadata is honored. +}); + +function createMockResource(type, data) +{ + const name = `${Math.floor(Date.now() * Math.random())}`; + + return { + url: `http://localhost/doesnt_exist/${name}`, + name, + type, + data, + metadata: {}, + }; +} + +function getJsonSpritesheet() +{ + /* eslint-disable */ + return {"frames": { + "0.png": + { + "frame": {"x":14,"y":28,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "1.png": + { + "frame": {"x":14,"y":42,"w":12,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, + "sourceSize": {"w":12,"h":14} + }, + "2.png": + { + "frame": {"x":14,"y":14,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "3.png": + { + "frame": {"x":42,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "4.png": + { + "frame": {"x":28,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "5.png": + { + "frame": {"x":14,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "6.png": + { + "frame": {"x":0,"y":42,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "7.png": + { + "frame": {"x":0,"y":28,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "8.png": + { + "frame": {"x":0,"y":14,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "9.png": + { + "frame": {"x":0,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }}, + "meta": { + "app": "http://www.texturepacker.com", + "version": "1.0", + "image": "hud.png", + "format": "RGBA8888", + "size": {"w":64,"h":64}, + "scale": "1", + "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" + } + }; + /* eslint-enable */ +} diff --git a/packages/spritesheet/test/index.js b/packages/spritesheet/test/index.js index 35d8688..8cf5636 100644 --- a/packages/spritesheet/test/index.js +++ b/packages/spritesheet/test/index.js @@ -1,105 +1,2 @@ -const { Spritesheet } = require('../'); -const { BaseTexture, Texture } = require('@pixi/core'); -const path = require('path'); - -describe('PIXI.Spritesheet', function () -{ - before(function () - { - this.resources = path.join(__dirname, 'resources'); - this.validate = function (spritesheet, done) - { - spritesheet.parse(function (textures) - { - const id = 'goldmine_10_5.png'; - const width = Math.floor(spritesheet.data.frames[id].frame.w); - const height = Math.floor(spritesheet.data.frames[id].frame.h); - - expect(Object.keys(textures).length).to.equal(1); - expect(Object.keys(spritesheet.textures).length).to.equal(1); - expect(textures[id]).to.be.an.instanceof(Texture); - expect(textures[id].width).to.equal(width / spritesheet.resolution); - expect(textures[id].height).to.equal(height / spritesheet.resolution); - expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); - spritesheet.destroy(true); - expect(spritesheet.textures).to.be.null; - expect(spritesheet.baseTexture).to.be.null; - done(); - }); - }; - }); - - it('should exist on PIXI', function () - { - expect(Spritesheet).to.be.a.function; - expect(Spritesheet.BATCH_SIZE).to.be.a.number; - }); - - it('should create an instance', function () - { - const baseTexture = new BaseTexture(); - const data = { - frames: {}, - meta: {}, - }; - - const spritesheet = new Spritesheet(baseTexture, data); - - expect(spritesheet.data).to.equal(data); - expect(spritesheet.baseTexture).to.equal(baseTexture); - expect(spritesheet.resolution).to.equal(1); - - spritesheet.destroy(true); - }); - - it('should create instance with scale resolution', function (done) - { - const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require - const image = new Image(); - - image.src = path.join(this.resources, data.meta.image); - image.onload = () => - { - const baseTexture = new BaseTexture(image, null, 1); - const spritesheet = new Spritesheet(baseTexture, data); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1.png'); - expect(spritesheet.resolution).to.equal(0.5); - this.validate(spritesheet, done); - }; - }); - - it('should create instance with BaseTexture source scale', function (done) - { - const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require - const baseTexture = new BaseTexture.fromImage(data.meta.image, undefined, undefined, 1.5); - const spritesheet = new Spritesheet(baseTexture, data); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1.png'); - expect(spritesheet.resolution).to.equal(0.5); - - this.validate(spritesheet, done); - }); - - it('should create instance with filename resolution', function (done) - { - const uri = path.resolve(this.resources, 'building1@2x.json'); - const data = require(uri); // eslint-disable-line global-require - const image = new Image(); - - image.src = path.join(this.resources, data.meta.image); - image.onload = () => - { - const baseTexture = new BaseTexture(image, null, 1); - const spritesheet = new Spritesheet(baseTexture, data, uri); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1@2x.png'); - expect(spritesheet.resolution).to.equal(2); - - this.validate(spritesheet, done); - }; - }); -}); +require('./Spritesheet'); +require('./SpritesheetLoader'); diff --git a/packages/text-bitmap/package.json b/packages/text-bitmap/package.json index a8a5f95..4b47da6 100644 --- a/packages/text-bitmap/package.json +++ b/packages/text-bitmap/package.json @@ -23,7 +23,7 @@ "build": "rollup -cp", "watch": "rollup -cw", "postversion": "npm run build", - "test": "exit 0" + "test": "tester" }, "files": [ "lib" @@ -31,12 +31,15 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha", "@pixi/sprite": "^5.0.0-alpha", "@pixi/settings": "^5.0.0-alpha", "@pixi/math": "^5.0.0-alpha", "@pixi/display": "^5.0.0-alpha" }, "devDependencies": { + "@pixi/spritesheet": "^5.0.0-alpha", + "@internal/tester": "^5.0.0-alpha", "@internal/builder": "^5.0.0-alpha", "rollup": "^0.50.0" } diff --git a/packages/text-bitmap/src/BitmapFontLoader.js b/packages/text-bitmap/src/BitmapFontLoader.js new file mode 100644 index 0000000..3956060 --- /dev/null +++ b/packages/text-bitmap/src/BitmapFontLoader.js @@ -0,0 +1,112 @@ +import * as path from 'path'; +import { TextureCache } from '@pixi/utils'; +import { Resource, Loader } from '@pixi/loaders'; +import BitmapText from './BitmapText'; + +/** + * {@link PIXI.loaders.Loader Loader} middleware for loading + * bitmap-based fonts suitable for using with {@link PIXI.BitmapText}. + * @class + * @memberof PIXI + */ +export default class BitmapFontLoader +{ + /** + * Register a BitmapText font from loader resource. + * + * @param {PIXI.loaders.Resource} resource - Loader resource. + * @param {PIXI.Texture} texture - Reference to texture. + */ + static parse(resource, texture) + { + resource.bitmapFont = BitmapText.registerFont(resource.data, texture); + } + + /** + * Middleware function to support BitmapText fonts, this is automatically installed. + * @see PIXI.loaders.Loader.useMiddleware + */ + static middleware() + { + return function bitmapFontLoader(resource, next) + { + // skip if no data or not xml data + if (!resource.data || resource.type !== Resource.TYPE.XML) + { + next(); + + return; + } + + // skip if not bitmap font data, using some silly duck-typing + if (resource.data.getElementsByTagName('page').length === 0 + || resource.data.getElementsByTagName('info').length === 0 + || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null + ) + { + next(); + + return; + } + + let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; + + if (resource.isDataUrl) + { + if (xmlUrl === '.') + { + xmlUrl = ''; + } + + if (this.baseUrl && xmlUrl) + { + // if baseurl has a trailing slash then add one to xmlUrl so the replace works below + if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') + { + xmlUrl += '/'; + } + } + } + + // remove baseUrl from xmlUrl + xmlUrl = xmlUrl.replace(this.baseUrl, ''); + + // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. + if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') + { + xmlUrl += '/'; + } + + const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); + + if (TextureCache[textureUrl]) + { + // reuse existing texture + BitmapFontLoader.parse(resource, TextureCache[textureUrl]); + next(); + } + else + { + const loadOptions = { + crossOrigin: resource.crossOrigin, + loadType: Resource.LOAD_TYPE.IMAGE, + metadata: resource.metadata.imageMetadata, + parentResource: resource, + }; + + // load the texture for the font + this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => + { + BitmapFontLoader.parse(resource, res.texture); + next(); + }); + } + }; + } +} + +// Add custom add support for loading fnt files as XML +Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); + +// Install loader support for BitmapText +Loader.useMiddleware(BitmapFontLoader.middleware); diff --git a/packages/text-bitmap/src/index.js b/packages/text-bitmap/src/index.js index a98e7b4..e5afd2c 100644 --- a/packages/text-bitmap/src/index.js +++ b/packages/text-bitmap/src/index.js @@ -1 +1,2 @@ export { default as BitmapText } from './BitmapText'; +export { default as BitmapFontLoader } from './BitmapFontLoader'; diff --git a/packages/text-bitmap/test/.eslintrc.json b/packages/text-bitmap/test/.eslintrc.json new file mode 100644 index 0000000..2094b04 --- /dev/null +++ b/packages/text-bitmap/test/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "globals": { + "expect": false, + "assert": false, + "sinon": false, + "PIXI": false + }, + "rules": { + "func-names": 0, + "no-unused-expressions": 0 + } +} \ No newline at end of file diff --git a/packages/text-bitmap/test/BitmapFontLoader.js b/packages/text-bitmap/test/BitmapFontLoader.js new file mode 100644 index 0000000..a76fc1c --- /dev/null +++ b/packages/text-bitmap/test/BitmapFontLoader.js @@ -0,0 +1,319 @@ +const path = require('path'); +const fs = require('fs'); +const { BaseTextureCache, TextureCache } = require('@pixi/utils'); +const { Texture, BaseTexture } = require('@pixi/core'); +const { Spritesheet } = require('@pixi/spritesheet'); +const { BitmapText, BitmapFontLoader } = require('../'); + +describe('PIXI.BitmapFontLoader', function () +{ + afterEach(function () + { + for (const font in BitmapText.fonts) + { + delete BitmapText.fonts[font]; + } + for (const baseTexture in BaseTextureCache) + { + delete BaseTextureCache[baseTexture]; + } + for (const texture in TextureCache) + { + delete TextureCache[texture]; + } + }); + + before(function (done) + { + const resolveURL = (url) => path.resolve(this.resources, url); + + this.resources = path.join(__dirname, 'resources'); + this.fontXML = null; + this.fontScaledXML = null; + this.fontImage = null; + this.fontScaledImage = null; + this.atlasImage = null; + this.atlasScaledImage = null; + this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require + this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require + + const loadXML = (url) => new Promise((resolve) => + fs.readFile(resolveURL(url), 'utf8', (err, data) => + { + expect(err).to.be.null; + resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); + })); + + const loadImage = (url) => new Promise((resolve) => + { + const image = new Image(); + + image.onload = () => resolve(image); + image.src = resolveURL(url); + }); + + Promise.all([ + loadXML('font.fnt'), + loadXML('font@0.5x.fnt'), + loadImage('font.png'), + loadImage('font@0.5x.png'), + loadImage('atlas.png'), + loadImage('atlas@0.5x.png'), + ]).then(([ + fontXML, + fontScaledXML, + fontImage, + fontScaledImage, + atlasImage, + atlasScaledImage, + ]) => + { + this.fontXML = fontXML; + this.fontScaledXML = fontScaledXML; + this.fontImage = fontImage; + this.fontScaledImage = fontScaledImage; + this.atlasImage = atlasImage; + this.atlasScaledImage = atlasScaledImage; + done(); + }); + }); + + it('should exist and return a function', function () + { + expect(BitmapFontLoader.middleware).to.be.a('function'); + expect(BitmapFontLoader.middleware()).to.be.a('function'); + }); + + it('should do nothing if the resource is not XML', function () + { + const spy = sinon.spy(); + const res = {}; + + BitmapFontLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should do nothing if the resource is not properly formatted XML', function () + { + const spy = sinon.spy(); + const res = { data: document.createDocumentFragment() }; + + BitmapFontLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + // TODO: Test the texture cache code path. + // TODO: Test the loading texture code path. + // TODO: Test data-url code paths. + + it('should properly register bitmap font', function (done) + { + const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); + 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charA.texture.frame.x).to.equal(2); + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charB.texture.frame.x).to.equal(2); + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charC.texture.frame.x).to.equal(23); + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charD.texture.frame.x).to.equal(19); + 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]; + + expect(charE).to.be.undefined; + done(); + }); + + it('should properly register SCALED bitmap font', function (done) + { + const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); + const font = BitmapText.registerFont(this.fontScaledXML, 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 + 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]; + + expect(charE).to.be.undefined; + done(); + }); + + it('should properly register bitmap font NESTED into spritesheet', function (done) + { + const baseTexture = new BaseTexture(this.atlasImage, null, 1); + const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); + + spritesheet.parse(() => + { + const fontTexture = Texture.fromFrame('resources/font.png'); + const font = BitmapText.registerFont(this.fontXML, fontTexture); + const fontX = 158; // bare value from spritesheet frame + const fontY = 2; // bare value from spritesheet frame + + 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charA.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charB.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charC.texture.frame.x).to.equal(fontX + 23); + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charD.texture.frame.x).to.equal(fontX + 19); + 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]; + + expect(charE).to.be.undefined; + done(); + }); + }); + + it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) + { + const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); + const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); + + spritesheet.resolution = 1; + + spritesheet.parse(() => + { + const fontTexture = Texture.fromFrame('resources/font.png'); + const font = BitmapText.registerFont(this.fontXML, fontTexture); + const fontX = 158; // bare value from spritesheet frame + const fontY = 2; // bare value from spritesheet frame + + 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charA.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charB.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charC.texture.frame.x).to.equal(fontX + 23); + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charD.texture.frame.x).to.equal(fontX + 19); + 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]; + + expect(charE).to.be.undefined; + done(); + }); + }); + + it('should parse exist', function () + { + expect(BitmapFontLoader.parse).to.be.a('function'); + }); +}); diff --git a/packages/text-bitmap/test/index.js b/packages/text-bitmap/test/index.js new file mode 100644 index 0000000..688fee2 --- /dev/null +++ b/packages/text-bitmap/test/index.js @@ -0,0 +1 @@ +require('./BitmapFontLoader'); diff --git a/packages/text-bitmap/test/resources/atlas.json b/packages/text-bitmap/test/resources/atlas.json new file mode 100644 index 0000000..86e65a5 --- /dev/null +++ b/packages/text-bitmap/test/resources/atlas.json @@ -0,0 +1,25 @@ +{ + "meta": { + "image": "atlas.png", + "size": {"w":256,"h":256}, + "scale": "1" + }, + "frames": { + "resources/test.png": + { + "frame": {"x":2,"y":2,"w":152,"h":188}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, + "sourceSize": {"w":300,"h":225} + }, + "resources/font.png": + { + "frame": {"x":158,"y":2,"w":40,"h":42}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, + "sourceSize": {"w":43,"h":46} + } + } +} \ No newline at end of file diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/packages/loaders/src/spritesheetParser.js b/packages/loaders/src/spritesheetParser.js deleted file mode 100644 index f7fa206..0000000 --- a/packages/loaders/src/spritesheetParser.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Resource } from 'resource-loader'; -import url from 'url'; -import { Spritesheet } from '@pixi/spritesheet'; - -export default function () -{ - return function spritesheetParser(resource, next) - { - const imageResourceName = `${resource.name}_image`; - - // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists - if (!resource.data - || resource.type !== Resource.TYPE.JSON - || !resource.data.frames - || this.resources[imageResourceName] - ) - { - next(); - - return; - } - - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - const resourcePath = getResourcePath(resource, this.baseUrl); - - // load the image for this sheet - this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) - { - const spritesheet = new Spritesheet( - res.texture.baseTexture, - resource.data, - resource.url - ); - - spritesheet.parse(() => - { - resource.spritesheet = spritesheet; - resource.textures = spritesheet.textures; - next(); - }); - }); - }; -} - -export function getResourcePath(resource, baseUrl) -{ - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - return resource.data.meta.image; - } - - return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); -} diff --git a/packages/loaders/test/bitmapFontParser.js b/packages/loaders/test/bitmapFontParser.js deleted file mode 100644 index 43ef194..0000000 --- a/packages/loaders/test/bitmapFontParser.js +++ /dev/null @@ -1,325 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const { BitmapText } = require('@pixi/text-bitmap'); -const { BaseTextureCache, TextureCache } = require('@pixi/utils'); -const { Texture, BaseTexture } = require('@pixi/core'); -const { Spritesheet } = require('@pixi/spritesheet'); -const { bitmapFontParser, parseBitmapFontData } = require('../'); - -describe('PIXI.loaders.bitmapFontParser', function () -{ - afterEach(function () - { - for (const font in BitmapText.fonts) - { - delete BitmapText.fonts[font]; - } - for (const baseTexture in BaseTextureCache) - { - delete BaseTextureCache[baseTexture]; - } - for (const texture in TextureCache) - { - delete TextureCache[texture]; - } - }); - - before(function (done) - { - const resolveURL = (url) => path.resolve(this.resources, url); - - this.resources = path.join(__dirname, 'resources'); - this.fontXML = null; - this.fontScaledXML = null; - this.fontImage = null; - this.fontScaledImage = null; - this.atlasImage = null; - this.atlasScaledImage = null; - this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require - this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require - - const loadXML = (url) => new Promise((resolve) => - fs.readFile(resolveURL(url), 'utf8', (err, data) => - { - expect(err).to.be.null; - resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); - })); - - const loadImage = (url) => new Promise((resolve) => - { - const image = new Image(); - - image.onload = () => resolve(image); - image.src = resolveURL(url); - }); - - Promise.all([ - loadXML('font.fnt'), - loadXML('font@0.5x.fnt'), - loadImage('font.png'), - loadImage('font@0.5x.png'), - loadImage('atlas.png'), - loadImage('atlas@0.5x.png'), - ]).then(([ - fontXML, - fontScaledXML, - fontImage, - fontScaledImage, - atlasImage, - atlasScaledImage, - ]) => - { - this.fontXML = fontXML; - this.fontScaledXML = fontScaledXML; - this.fontImage = fontImage; - this.fontScaledImage = fontScaledImage; - this.atlasImage = atlasImage; - this.atlasScaledImage = atlasScaledImage; - done(); - }); - }); - - it('should exist and return a function', function () - { - expect(bitmapFontParser).to.be.a('function'); - expect(bitmapFontParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not XML', function () - { - const spy = sinon.spy(); - const res = {}; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is not properly formatted XML', function () - { - const spy = sinon.spy(); - const res = { data: document.createDocumentFragment() }; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - // TODO: Test the texture cache code path. - // TODO: Test the loading texture code path. - // TODO: Test data-url code paths. - - it('should properly register bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charA.texture.frame.x).to.equal(2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charB.texture.frame.x).to.equal(2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charC.texture.frame.x).to.equal(23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charD.texture.frame.x).to.equal(19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register SCALED bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); - const font = BitmapText.registerFont(this.fontScaledXML, 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register bitmap font NESTED into spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); - - it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); - - spritesheet.resolution = 1; - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); -}); - -describe('PIXI.loaders.parseBitmapFontData', function () -{ - it('should exist', function () - { - expect(parseBitmapFontData).to.be.a('function'); - }); - - // TODO: Test the parser code. -}); diff --git a/packages/loaders/test/index.js b/packages/loaders/test/index.js index f00de5f..72c6554 100644 --- a/packages/loaders/test/index.js +++ b/packages/loaders/test/index.js @@ -1,4 +1,2 @@ -require('./bitmapFontParser'); require('./loader'); -require('./spritesheetParser'); require('./textureParser'); diff --git a/packages/loaders/test/resources/atlas.json b/packages/loaders/test/resources/atlas.json deleted file mode 100644 index 86e65a5..0000000 --- a/packages/loaders/test/resources/atlas.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas.png b/packages/loaders/test/resources/atlas.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/atlas@0.5x.json b/packages/loaders/test/resources/atlas@0.5x.json deleted file mode 100644 index ae990a1..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas@0.5x.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas@0.5x.png b/packages/loaders/test/resources/atlas@0.5x.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font.fnt b/packages/loaders/test/resources/font.fnt deleted file mode 100644 index 56e1060..0000000 --- a/packages/loaders/test/resources/font.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font.png b/packages/loaders/test/resources/font.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font@0.5x.fnt b/packages/loaders/test/resources/font@0.5x.fnt deleted file mode 100644 index 6c247c7..0000000 --- a/packages/loaders/test/resources/font@0.5x.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font@0.5x.png b/packages/loaders/test/resources/font@0.5x.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/spritesheetParser.js b/packages/loaders/test/spritesheetParser.js deleted file mode 100644 index 98890b8..0000000 --- a/packages/loaders/test/spritesheetParser.js +++ /dev/null @@ -1,213 +0,0 @@ -const path = require('path'); -const { spritesheetParser, Resource, Loader, getResourcePath } = require('../'); -const { Texture, BaseTexture } = require('@pixi/core'); - -describe('PIXI.loaders.spritesheetParser', function () -{ - it('should exist and return a function', function () - { - expect(spritesheetParser).to.be.a('function'); - expect(spritesheetParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not JSON', function () - { - const spy = sinon.spy(); - const res = {}; - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is JSON, but improper format', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, {}); - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should load the image & create textures if json is properly formatted', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); - const loader = new Loader(); - const addStub = sinon.stub(loader, 'add'); - const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); - - imgRes.texture = new Texture(new BaseTexture(imgRes.data)); - - addStub.yields(imgRes); - - spritesheetParser().call(loader, res, spy); - - addStub.restore(); - - expect(spy).to.have.been.calledOnce; - expect(addStub).to.have.been.calledWith( - `${res.name}_image`, - `${path.dirname(res.url)}/${res.data.meta.image}` - ); - expect(res).to.have.property('textures') - .that.is.an('object') - .with.keys(Object.keys(getJsonSpritesheet().frames)) - .and.has.property('0.png') - .that.is.an.instanceof(Texture); - }); - - it('should build the image url', function () - { - function getPath(url, image) - { - return getResourcePath({ - url, - data: { meta: { image } }, - }); - } - - let result = getPath('http://some.com/spritesheet.json', 'img.png'); - - expect(result).to.be.equals('http://some.com/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('http://some.com/some/img.png'); - - result = getPath('/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/img.png'); - - result = getPath('/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('/some/img.png'); - }); - - // TODO: Test that rectangles are created correctly. - // TODO: Test that bathc processing works correctly. - // TODO: Test that resolution processing works correctly. - // TODO: Test that metadata is honored. -}); - -function createMockResource(type, data) -{ - const name = `${Math.floor(Date.now() * Math.random())}`; - - return { - url: `http://localhost/doesnt_exist/${name}`, - name, - type, - data, - metadata: {}, - }; -} - -function getJsonSpritesheet() -{ - /* eslint-disable */ - return {"frames": { - "0.png": - { - "frame": {"x":14,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "1.png": - { - "frame": {"x":14,"y":42,"w":12,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, - "sourceSize": {"w":12,"h":14} - }, - "2.png": - { - "frame": {"x":14,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "3.png": - { - "frame": {"x":42,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "4.png": - { - "frame": {"x":28,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "5.png": - { - "frame": {"x":14,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "6.png": - { - "frame": {"x":0,"y":42,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "7.png": - { - "frame": {"x":0,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "8.png": - { - "frame": {"x":0,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "9.png": - { - "frame": {"x":0,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }}, - "meta": { - "app": "http://www.texturepacker.com", - "version": "1.0", - "image": "hud.png", - "format": "RGBA8888", - "size": {"w":64,"h":64}, - "scale": "1", - "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" - } - }; - /* eslint-enable */ -} diff --git a/packages/mixin-app-loader/LICENSE b/packages/mixin-app-loader/LICENSE new file mode 100644 index 0000000..5d16b50 --- /dev/null +++ b/packages/mixin-app-loader/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2013-2017 Mathew Groves, Chad Engler + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/packages/mixin-app-loader/README.md b/packages/mixin-app-loader/README.md new file mode 100644 index 0000000..3e89f86 --- /dev/null +++ b/packages/mixin-app-loader/README.md @@ -0,0 +1,13 @@ +# @pixi/mixin-app-loader + +## Installation + +```bash +npm install @pixi/mixin-app-loader +``` + +## Usage + +```js +import '@pixi/mixin-app-loader'; +``` \ No newline at end of file diff --git a/packages/mixin-app-loader/package.json b/packages/mixin-app-loader/package.json new file mode 100644 index 0000000..869ed4e --- /dev/null +++ b/packages/mixin-app-loader/package.json @@ -0,0 +1,41 @@ +{ + "name": "@pixi/mixin-app-loader", + "version": "5.0.0-alpha", + "main": "lib/mixin-app-loader.js", + "module": "lib/mixin-app-loader.es.js", + "description": "Support for loader in Application", + "author": "Mat Groves", + "contributors": [ + "Matt Karl " + ], + "homepage": "http://pixijs.com/", + "bugs": "https://github.com/pixijs/pixi.js/issues", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/pixijs/pixi.js.git" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "build:dev": "rollup -c", + "build": "rollup -cp", + "watch": "rollup -cw", + "postversion": "npm run build", + "test": "tester" + }, + "files": [ + "lib" + ], + "dependencies": { + "@pixi/app": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" + }, + "devDependencies": { + "@pixi/utils": "^5.0.0-alpha", + "@internal/tester": "^5.0.0-alpha", + "@internal/builder": "^5.0.0-alpha", + "rollup": "^0.50.0" + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/rollup.config.js b/packages/mixin-app-loader/rollup.config.js new file mode 100644 index 0000000..a73c874 --- /dev/null +++ b/packages/mixin-app-loader/rollup.config.js @@ -0,0 +1 @@ +export { default } from '@internal/builder'; diff --git a/packages/mixin-app-loader/src/index.js b/packages/mixin-app-loader/src/index.js new file mode 100644 index 0000000..354997a --- /dev/null +++ b/packages/mixin-app-loader/src/index.js @@ -0,0 +1,38 @@ +import { Application } from '@pixi/app'; +import { Loader, shared } from '@pixi/loaders'; + +Application.prototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperties(Application.prototype, { + loader: { + get() + { + if (!this._loader && this._options) + { + const { sharedLoader } = this._options; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +Application.prototype._parentDestroy = Application.prototype.destroy; +Application.prototype.destroy = function destroy(removeView) +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(removeView); +}; diff --git a/packages/mixin-app-loader/test/.eslintrc.json b/packages/mixin-app-loader/test/.eslintrc.json new file mode 100644 index 0000000..2094b04 --- /dev/null +++ b/packages/mixin-app-loader/test/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "globals": { + "expect": false, + "assert": false, + "sinon": false, + "PIXI": false + }, + "rules": { + "func-names": 0, + "no-unused-expressions": 0 + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/test/index.js b/packages/mixin-app-loader/test/index.js new file mode 100644 index 0000000..3468fe5 --- /dev/null +++ b/packages/mixin-app-loader/test/index.js @@ -0,0 +1,35 @@ +const { Application } = require('@pixi/app'); +const { Loader, shared } = require('@pixi/loaders'); +const { skipHello } = require('@pixi/utils'); + +require('../'); + +skipHello(); + +describe('PIXI.Application#loader', function () +{ + it('should contain loader property', function () + { + const obj = new Application(); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); + + it('should use sharedLoader option', function () + { + const obj = new Application({ sharedLoader: true }); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + expect(obj.loader).to.equal(shared); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); +}); diff --git a/packages/spritesheet/package.json b/packages/spritesheet/package.json index 34749e2..5f35442 100644 --- a/packages/spritesheet/package.json +++ b/packages/spritesheet/package.json @@ -31,7 +31,8 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/math": "^5.0.0-alpha", - "@pixi/utils": "^5.0.0-alpha" + "@pixi/utils": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" }, "devDependencies": { "@internal/builder": "^5.0.0-alpha", diff --git a/packages/spritesheet/src/SpritesheetLoader.js b/packages/spritesheet/src/SpritesheetLoader.js new file mode 100644 index 0000000..c7b34ea --- /dev/null +++ b/packages/spritesheet/src/SpritesheetLoader.js @@ -0,0 +1,83 @@ +import url from 'url'; +import { Resource, Loader } from '@pixi/loaders'; +import Spritesheet from './Spritesheet'; + +/** + * {@link PIXI.loaders.Loader Loader} middleware for loading + * texture atlases that have been created with TexturePacker or + * similar JSON-based spritesheet. This automatically generates + * Texture resources. + * @class + * @memberof PIXI + */ +export default class SpritesheetLoader +{ + /** + * Middleware function to support Spritesheets, this is automatically installed. + * @see PIXI.loaders.Loader.useMiddleware + */ + static middleware() + { + return function spritesheetLoader(resource, next) + { + const imageResourceName = `${resource.name}_image`; + + // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists + if (!resource.data + || resource.type !== Resource.TYPE.JSON + || !resource.data.frames + || this.resources[imageResourceName] + ) + { + next(); + + return; + } + + const loadOptions = { + crossOrigin: resource.crossOrigin, + loadType: Resource.LOAD_TYPE.IMAGE, + metadata: resource.metadata.imageMetadata, + parentResource: resource, + }; + + const resourcePath = SpritesheetLoader.getResourcePath(resource, this.baseUrl); + + // load the image for this sheet + this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) + { + const spritesheet = new Spritesheet( + res.texture.baseTexture, + resource.data, + resource.url + ); + + spritesheet.parse(() => + { + resource.spritesheet = spritesheet; + resource.textures = spritesheet.textures; + next(); + }); + }); + }; + } + + /** + * Get the spritesheets root path + * @param {PIXI.loader.Resource} resource - Resource to check path + * @param {string} baseUrl - Base root url + */ + static getResourcePath(resource, baseUrl) + { + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); + } +} + +// Install loader support for Spritesheet objects +Loader.useMiddleware(SpritesheetLoader.middleware); diff --git a/packages/spritesheet/src/index.js b/packages/spritesheet/src/index.js index 51d96a1..b9ee1e8 100644 --- a/packages/spritesheet/src/index.js +++ b/packages/spritesheet/src/index.js @@ -1 +1,2 @@ export { default as Spritesheet } from './Spritesheet'; +export { default as SpritesheetLoader } from './SpritesheetLoader'; diff --git a/packages/spritesheet/test/Spritesheet.js b/packages/spritesheet/test/Spritesheet.js new file mode 100644 index 0000000..35d8688 --- /dev/null +++ b/packages/spritesheet/test/Spritesheet.js @@ -0,0 +1,105 @@ +const { Spritesheet } = require('../'); +const { BaseTexture, Texture } = require('@pixi/core'); +const path = require('path'); + +describe('PIXI.Spritesheet', function () +{ + before(function () + { + this.resources = path.join(__dirname, 'resources'); + this.validate = function (spritesheet, done) + { + spritesheet.parse(function (textures) + { + const id = 'goldmine_10_5.png'; + const width = Math.floor(spritesheet.data.frames[id].frame.w); + const height = Math.floor(spritesheet.data.frames[id].frame.h); + + expect(Object.keys(textures).length).to.equal(1); + expect(Object.keys(spritesheet.textures).length).to.equal(1); + expect(textures[id]).to.be.an.instanceof(Texture); + expect(textures[id].width).to.equal(width / spritesheet.resolution); + expect(textures[id].height).to.equal(height / spritesheet.resolution); + expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); + spritesheet.destroy(true); + expect(spritesheet.textures).to.be.null; + expect(spritesheet.baseTexture).to.be.null; + done(); + }); + }; + }); + + it('should exist on PIXI', function () + { + expect(Spritesheet).to.be.a.function; + expect(Spritesheet.BATCH_SIZE).to.be.a.number; + }); + + it('should create an instance', function () + { + const baseTexture = new BaseTexture(); + const data = { + frames: {}, + meta: {}, + }; + + const spritesheet = new Spritesheet(baseTexture, data); + + expect(spritesheet.data).to.equal(data); + expect(spritesheet.baseTexture).to.equal(baseTexture); + expect(spritesheet.resolution).to.equal(1); + + spritesheet.destroy(true); + }); + + it('should create instance with scale resolution', function (done) + { + const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require + const image = new Image(); + + image.src = path.join(this.resources, data.meta.image); + image.onload = () => + { + const baseTexture = new BaseTexture(image, null, 1); + const spritesheet = new Spritesheet(baseTexture, data); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1.png'); + expect(spritesheet.resolution).to.equal(0.5); + this.validate(spritesheet, done); + }; + }); + + it('should create instance with BaseTexture source scale', function (done) + { + const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require + const baseTexture = new BaseTexture.fromImage(data.meta.image, undefined, undefined, 1.5); + const spritesheet = new Spritesheet(baseTexture, data); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1.png'); + expect(spritesheet.resolution).to.equal(0.5); + + this.validate(spritesheet, done); + }); + + it('should create instance with filename resolution', function (done) + { + const uri = path.resolve(this.resources, 'building1@2x.json'); + const data = require(uri); // eslint-disable-line global-require + const image = new Image(); + + image.src = path.join(this.resources, data.meta.image); + image.onload = () => + { + const baseTexture = new BaseTexture(image, null, 1); + const spritesheet = new Spritesheet(baseTexture, data, uri); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1@2x.png'); + expect(spritesheet.resolution).to.equal(2); + + this.validate(spritesheet, done); + }; + }); +}); diff --git a/packages/spritesheet/test/SpritesheetLoader.js b/packages/spritesheet/test/SpritesheetLoader.js new file mode 100644 index 0000000..e4205ec --- /dev/null +++ b/packages/spritesheet/test/SpritesheetLoader.js @@ -0,0 +1,214 @@ +const path = require('path'); +const { Loader, Resource } = require('@pixi/loaders'); +const { Texture, BaseTexture } = require('@pixi/core'); +const { SpritesheetLoader } = require('../'); + +describe('PIXI.SpritesheetLoader', function () +{ + it('should exist and return a function', function () + { + expect(SpritesheetLoader.middleware).to.be.a('function'); + expect(SpritesheetLoader.middleware()).to.be.a('function'); + }); + + it('should do nothing if the resource is not JSON', function () + { + const spy = sinon.spy(); + const res = {}; + + SpritesheetLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should do nothing if the resource is JSON, but improper format', function () + { + const spy = sinon.spy(); + const res = createMockResource(Resource.TYPE.JSON, {}); + + SpritesheetLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should load the image & create textures if json is properly formatted', function () + { + const spy = sinon.spy(); + const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); + const loader = new Loader(); + const addStub = sinon.stub(loader, 'add'); + const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); + + imgRes.texture = new Texture(new BaseTexture(imgRes.data)); + + addStub.yields(imgRes); + + SpritesheetLoader.middleware().call(loader, res, spy); + + addStub.restore(); + + expect(spy).to.have.been.calledOnce; + expect(addStub).to.have.been.calledWith( + `${res.name}_image`, + `${path.dirname(res.url)}/${res.data.meta.image}` + ); + expect(res).to.have.property('textures') + .that.is.an('object') + .with.keys(Object.keys(getJsonSpritesheet().frames)) + .and.has.property('0.png') + .that.is.an.instanceof(Texture); + }); + + it('should build the image url', function () + { + function getPath(url, image) + { + return SpritesheetLoader.getResourcePath({ + url, + data: { meta: { image } }, + }); + } + + let result = getPath('http://some.com/spritesheet.json', 'img.png'); + + expect(result).to.be.equals('http://some.com/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); + expect(result).to.be.equals('http://some.com/some/dir/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); + expect(result).to.be.equals('http://some.com/some/dir/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); + expect(result).to.be.equals('http://some.com/some/img.png'); + + result = getPath('/spritesheet.json', 'img.png'); + expect(result).to.be.equals('/img.png'); + + result = getPath('/some/dir/spritesheet.json', 'img.png'); + expect(result).to.be.equals('/some/dir/img.png'); + + result = getPath('/some/dir/spritesheet.json', './img.png'); + expect(result).to.be.equals('/some/dir/img.png'); + + result = getPath('/some/dir/spritesheet.json', '../img.png'); + expect(result).to.be.equals('/some/img.png'); + }); + + // TODO: Test that rectangles are created correctly. + // TODO: Test that bathc processing works correctly. + // TODO: Test that resolution processing works correctly. + // TODO: Test that metadata is honored. +}); + +function createMockResource(type, data) +{ + const name = `${Math.floor(Date.now() * Math.random())}`; + + return { + url: `http://localhost/doesnt_exist/${name}`, + name, + type, + data, + metadata: {}, + }; +} + +function getJsonSpritesheet() +{ + /* eslint-disable */ + return {"frames": { + "0.png": + { + "frame": {"x":14,"y":28,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "1.png": + { + "frame": {"x":14,"y":42,"w":12,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, + "sourceSize": {"w":12,"h":14} + }, + "2.png": + { + "frame": {"x":14,"y":14,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "3.png": + { + "frame": {"x":42,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "4.png": + { + "frame": {"x":28,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "5.png": + { + "frame": {"x":14,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "6.png": + { + "frame": {"x":0,"y":42,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "7.png": + { + "frame": {"x":0,"y":28,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "8.png": + { + "frame": {"x":0,"y":14,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "9.png": + { + "frame": {"x":0,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }}, + "meta": { + "app": "http://www.texturepacker.com", + "version": "1.0", + "image": "hud.png", + "format": "RGBA8888", + "size": {"w":64,"h":64}, + "scale": "1", + "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" + } + }; + /* eslint-enable */ +} diff --git a/packages/spritesheet/test/index.js b/packages/spritesheet/test/index.js index 35d8688..8cf5636 100644 --- a/packages/spritesheet/test/index.js +++ b/packages/spritesheet/test/index.js @@ -1,105 +1,2 @@ -const { Spritesheet } = require('../'); -const { BaseTexture, Texture } = require('@pixi/core'); -const path = require('path'); - -describe('PIXI.Spritesheet', function () -{ - before(function () - { - this.resources = path.join(__dirname, 'resources'); - this.validate = function (spritesheet, done) - { - spritesheet.parse(function (textures) - { - const id = 'goldmine_10_5.png'; - const width = Math.floor(spritesheet.data.frames[id].frame.w); - const height = Math.floor(spritesheet.data.frames[id].frame.h); - - expect(Object.keys(textures).length).to.equal(1); - expect(Object.keys(spritesheet.textures).length).to.equal(1); - expect(textures[id]).to.be.an.instanceof(Texture); - expect(textures[id].width).to.equal(width / spritesheet.resolution); - expect(textures[id].height).to.equal(height / spritesheet.resolution); - expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); - spritesheet.destroy(true); - expect(spritesheet.textures).to.be.null; - expect(spritesheet.baseTexture).to.be.null; - done(); - }); - }; - }); - - it('should exist on PIXI', function () - { - expect(Spritesheet).to.be.a.function; - expect(Spritesheet.BATCH_SIZE).to.be.a.number; - }); - - it('should create an instance', function () - { - const baseTexture = new BaseTexture(); - const data = { - frames: {}, - meta: {}, - }; - - const spritesheet = new Spritesheet(baseTexture, data); - - expect(spritesheet.data).to.equal(data); - expect(spritesheet.baseTexture).to.equal(baseTexture); - expect(spritesheet.resolution).to.equal(1); - - spritesheet.destroy(true); - }); - - it('should create instance with scale resolution', function (done) - { - const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require - const image = new Image(); - - image.src = path.join(this.resources, data.meta.image); - image.onload = () => - { - const baseTexture = new BaseTexture(image, null, 1); - const spritesheet = new Spritesheet(baseTexture, data); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1.png'); - expect(spritesheet.resolution).to.equal(0.5); - this.validate(spritesheet, done); - }; - }); - - it('should create instance with BaseTexture source scale', function (done) - { - const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require - const baseTexture = new BaseTexture.fromImage(data.meta.image, undefined, undefined, 1.5); - const spritesheet = new Spritesheet(baseTexture, data); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1.png'); - expect(spritesheet.resolution).to.equal(0.5); - - this.validate(spritesheet, done); - }); - - it('should create instance with filename resolution', function (done) - { - const uri = path.resolve(this.resources, 'building1@2x.json'); - const data = require(uri); // eslint-disable-line global-require - const image = new Image(); - - image.src = path.join(this.resources, data.meta.image); - image.onload = () => - { - const baseTexture = new BaseTexture(image, null, 1); - const spritesheet = new Spritesheet(baseTexture, data, uri); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1@2x.png'); - expect(spritesheet.resolution).to.equal(2); - - this.validate(spritesheet, done); - }; - }); -}); +require('./Spritesheet'); +require('./SpritesheetLoader'); diff --git a/packages/text-bitmap/package.json b/packages/text-bitmap/package.json index a8a5f95..4b47da6 100644 --- a/packages/text-bitmap/package.json +++ b/packages/text-bitmap/package.json @@ -23,7 +23,7 @@ "build": "rollup -cp", "watch": "rollup -cw", "postversion": "npm run build", - "test": "exit 0" + "test": "tester" }, "files": [ "lib" @@ -31,12 +31,15 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha", "@pixi/sprite": "^5.0.0-alpha", "@pixi/settings": "^5.0.0-alpha", "@pixi/math": "^5.0.0-alpha", "@pixi/display": "^5.0.0-alpha" }, "devDependencies": { + "@pixi/spritesheet": "^5.0.0-alpha", + "@internal/tester": "^5.0.0-alpha", "@internal/builder": "^5.0.0-alpha", "rollup": "^0.50.0" } diff --git a/packages/text-bitmap/src/BitmapFontLoader.js b/packages/text-bitmap/src/BitmapFontLoader.js new file mode 100644 index 0000000..3956060 --- /dev/null +++ b/packages/text-bitmap/src/BitmapFontLoader.js @@ -0,0 +1,112 @@ +import * as path from 'path'; +import { TextureCache } from '@pixi/utils'; +import { Resource, Loader } from '@pixi/loaders'; +import BitmapText from './BitmapText'; + +/** + * {@link PIXI.loaders.Loader Loader} middleware for loading + * bitmap-based fonts suitable for using with {@link PIXI.BitmapText}. + * @class + * @memberof PIXI + */ +export default class BitmapFontLoader +{ + /** + * Register a BitmapText font from loader resource. + * + * @param {PIXI.loaders.Resource} resource - Loader resource. + * @param {PIXI.Texture} texture - Reference to texture. + */ + static parse(resource, texture) + { + resource.bitmapFont = BitmapText.registerFont(resource.data, texture); + } + + /** + * Middleware function to support BitmapText fonts, this is automatically installed. + * @see PIXI.loaders.Loader.useMiddleware + */ + static middleware() + { + return function bitmapFontLoader(resource, next) + { + // skip if no data or not xml data + if (!resource.data || resource.type !== Resource.TYPE.XML) + { + next(); + + return; + } + + // skip if not bitmap font data, using some silly duck-typing + if (resource.data.getElementsByTagName('page').length === 0 + || resource.data.getElementsByTagName('info').length === 0 + || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null + ) + { + next(); + + return; + } + + let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; + + if (resource.isDataUrl) + { + if (xmlUrl === '.') + { + xmlUrl = ''; + } + + if (this.baseUrl && xmlUrl) + { + // if baseurl has a trailing slash then add one to xmlUrl so the replace works below + if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') + { + xmlUrl += '/'; + } + } + } + + // remove baseUrl from xmlUrl + xmlUrl = xmlUrl.replace(this.baseUrl, ''); + + // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. + if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') + { + xmlUrl += '/'; + } + + const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); + + if (TextureCache[textureUrl]) + { + // reuse existing texture + BitmapFontLoader.parse(resource, TextureCache[textureUrl]); + next(); + } + else + { + const loadOptions = { + crossOrigin: resource.crossOrigin, + loadType: Resource.LOAD_TYPE.IMAGE, + metadata: resource.metadata.imageMetadata, + parentResource: resource, + }; + + // load the texture for the font + this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => + { + BitmapFontLoader.parse(resource, res.texture); + next(); + }); + } + }; + } +} + +// Add custom add support for loading fnt files as XML +Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); + +// Install loader support for BitmapText +Loader.useMiddleware(BitmapFontLoader.middleware); diff --git a/packages/text-bitmap/src/index.js b/packages/text-bitmap/src/index.js index a98e7b4..e5afd2c 100644 --- a/packages/text-bitmap/src/index.js +++ b/packages/text-bitmap/src/index.js @@ -1 +1,2 @@ export { default as BitmapText } from './BitmapText'; +export { default as BitmapFontLoader } from './BitmapFontLoader'; diff --git a/packages/text-bitmap/test/.eslintrc.json b/packages/text-bitmap/test/.eslintrc.json new file mode 100644 index 0000000..2094b04 --- /dev/null +++ b/packages/text-bitmap/test/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "globals": { + "expect": false, + "assert": false, + "sinon": false, + "PIXI": false + }, + "rules": { + "func-names": 0, + "no-unused-expressions": 0 + } +} \ No newline at end of file diff --git a/packages/text-bitmap/test/BitmapFontLoader.js b/packages/text-bitmap/test/BitmapFontLoader.js new file mode 100644 index 0000000..a76fc1c --- /dev/null +++ b/packages/text-bitmap/test/BitmapFontLoader.js @@ -0,0 +1,319 @@ +const path = require('path'); +const fs = require('fs'); +const { BaseTextureCache, TextureCache } = require('@pixi/utils'); +const { Texture, BaseTexture } = require('@pixi/core'); +const { Spritesheet } = require('@pixi/spritesheet'); +const { BitmapText, BitmapFontLoader } = require('../'); + +describe('PIXI.BitmapFontLoader', function () +{ + afterEach(function () + { + for (const font in BitmapText.fonts) + { + delete BitmapText.fonts[font]; + } + for (const baseTexture in BaseTextureCache) + { + delete BaseTextureCache[baseTexture]; + } + for (const texture in TextureCache) + { + delete TextureCache[texture]; + } + }); + + before(function (done) + { + const resolveURL = (url) => path.resolve(this.resources, url); + + this.resources = path.join(__dirname, 'resources'); + this.fontXML = null; + this.fontScaledXML = null; + this.fontImage = null; + this.fontScaledImage = null; + this.atlasImage = null; + this.atlasScaledImage = null; + this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require + this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require + + const loadXML = (url) => new Promise((resolve) => + fs.readFile(resolveURL(url), 'utf8', (err, data) => + { + expect(err).to.be.null; + resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); + })); + + const loadImage = (url) => new Promise((resolve) => + { + const image = new Image(); + + image.onload = () => resolve(image); + image.src = resolveURL(url); + }); + + Promise.all([ + loadXML('font.fnt'), + loadXML('font@0.5x.fnt'), + loadImage('font.png'), + loadImage('font@0.5x.png'), + loadImage('atlas.png'), + loadImage('atlas@0.5x.png'), + ]).then(([ + fontXML, + fontScaledXML, + fontImage, + fontScaledImage, + atlasImage, + atlasScaledImage, + ]) => + { + this.fontXML = fontXML; + this.fontScaledXML = fontScaledXML; + this.fontImage = fontImage; + this.fontScaledImage = fontScaledImage; + this.atlasImage = atlasImage; + this.atlasScaledImage = atlasScaledImage; + done(); + }); + }); + + it('should exist and return a function', function () + { + expect(BitmapFontLoader.middleware).to.be.a('function'); + expect(BitmapFontLoader.middleware()).to.be.a('function'); + }); + + it('should do nothing if the resource is not XML', function () + { + const spy = sinon.spy(); + const res = {}; + + BitmapFontLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should do nothing if the resource is not properly formatted XML', function () + { + const spy = sinon.spy(); + const res = { data: document.createDocumentFragment() }; + + BitmapFontLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + // TODO: Test the texture cache code path. + // TODO: Test the loading texture code path. + // TODO: Test data-url code paths. + + it('should properly register bitmap font', function (done) + { + const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); + 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charA.texture.frame.x).to.equal(2); + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charB.texture.frame.x).to.equal(2); + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charC.texture.frame.x).to.equal(23); + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charD.texture.frame.x).to.equal(19); + 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]; + + expect(charE).to.be.undefined; + done(); + }); + + it('should properly register SCALED bitmap font', function (done) + { + const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); + const font = BitmapText.registerFont(this.fontScaledXML, 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 + 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]; + + expect(charE).to.be.undefined; + done(); + }); + + it('should properly register bitmap font NESTED into spritesheet', function (done) + { + const baseTexture = new BaseTexture(this.atlasImage, null, 1); + const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); + + spritesheet.parse(() => + { + const fontTexture = Texture.fromFrame('resources/font.png'); + const font = BitmapText.registerFont(this.fontXML, fontTexture); + const fontX = 158; // bare value from spritesheet frame + const fontY = 2; // bare value from spritesheet frame + + 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charA.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charB.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charC.texture.frame.x).to.equal(fontX + 23); + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charD.texture.frame.x).to.equal(fontX + 19); + 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]; + + expect(charE).to.be.undefined; + done(); + }); + }); + + it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) + { + const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); + const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); + + spritesheet.resolution = 1; + + spritesheet.parse(() => + { + const fontTexture = Texture.fromFrame('resources/font.png'); + const font = BitmapText.registerFont(this.fontXML, fontTexture); + const fontX = 158; // bare value from spritesheet frame + const fontY = 2; // bare value from spritesheet frame + + 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charA.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charB.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charC.texture.frame.x).to.equal(fontX + 23); + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charD.texture.frame.x).to.equal(fontX + 19); + 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]; + + expect(charE).to.be.undefined; + done(); + }); + }); + + it('should parse exist', function () + { + expect(BitmapFontLoader.parse).to.be.a('function'); + }); +}); diff --git a/packages/text-bitmap/test/index.js b/packages/text-bitmap/test/index.js new file mode 100644 index 0000000..688fee2 --- /dev/null +++ b/packages/text-bitmap/test/index.js @@ -0,0 +1 @@ +require('./BitmapFontLoader'); diff --git a/packages/text-bitmap/test/resources/atlas.json b/packages/text-bitmap/test/resources/atlas.json new file mode 100644 index 0000000..86e65a5 --- /dev/null +++ b/packages/text-bitmap/test/resources/atlas.json @@ -0,0 +1,25 @@ +{ + "meta": { + "image": "atlas.png", + "size": {"w":256,"h":256}, + "scale": "1" + }, + "frames": { + "resources/test.png": + { + "frame": {"x":2,"y":2,"w":152,"h":188}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, + "sourceSize": {"w":300,"h":225} + }, + "resources/font.png": + { + "frame": {"x":158,"y":2,"w":40,"h":42}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, + "sourceSize": {"w":43,"h":46} + } + } +} \ No newline at end of file diff --git a/packages/text-bitmap/test/resources/atlas.png b/packages/text-bitmap/test/resources/atlas.png new file mode 100644 index 0000000..d5e7892 --- /dev/null +++ b/packages/text-bitmap/test/resources/atlas.png Binary files differ diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/packages/loaders/src/spritesheetParser.js b/packages/loaders/src/spritesheetParser.js deleted file mode 100644 index f7fa206..0000000 --- a/packages/loaders/src/spritesheetParser.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Resource } from 'resource-loader'; -import url from 'url'; -import { Spritesheet } from '@pixi/spritesheet'; - -export default function () -{ - return function spritesheetParser(resource, next) - { - const imageResourceName = `${resource.name}_image`; - - // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists - if (!resource.data - || resource.type !== Resource.TYPE.JSON - || !resource.data.frames - || this.resources[imageResourceName] - ) - { - next(); - - return; - } - - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - const resourcePath = getResourcePath(resource, this.baseUrl); - - // load the image for this sheet - this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) - { - const spritesheet = new Spritesheet( - res.texture.baseTexture, - resource.data, - resource.url - ); - - spritesheet.parse(() => - { - resource.spritesheet = spritesheet; - resource.textures = spritesheet.textures; - next(); - }); - }); - }; -} - -export function getResourcePath(resource, baseUrl) -{ - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - return resource.data.meta.image; - } - - return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); -} diff --git a/packages/loaders/test/bitmapFontParser.js b/packages/loaders/test/bitmapFontParser.js deleted file mode 100644 index 43ef194..0000000 --- a/packages/loaders/test/bitmapFontParser.js +++ /dev/null @@ -1,325 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const { BitmapText } = require('@pixi/text-bitmap'); -const { BaseTextureCache, TextureCache } = require('@pixi/utils'); -const { Texture, BaseTexture } = require('@pixi/core'); -const { Spritesheet } = require('@pixi/spritesheet'); -const { bitmapFontParser, parseBitmapFontData } = require('../'); - -describe('PIXI.loaders.bitmapFontParser', function () -{ - afterEach(function () - { - for (const font in BitmapText.fonts) - { - delete BitmapText.fonts[font]; - } - for (const baseTexture in BaseTextureCache) - { - delete BaseTextureCache[baseTexture]; - } - for (const texture in TextureCache) - { - delete TextureCache[texture]; - } - }); - - before(function (done) - { - const resolveURL = (url) => path.resolve(this.resources, url); - - this.resources = path.join(__dirname, 'resources'); - this.fontXML = null; - this.fontScaledXML = null; - this.fontImage = null; - this.fontScaledImage = null; - this.atlasImage = null; - this.atlasScaledImage = null; - this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require - this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require - - const loadXML = (url) => new Promise((resolve) => - fs.readFile(resolveURL(url), 'utf8', (err, data) => - { - expect(err).to.be.null; - resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); - })); - - const loadImage = (url) => new Promise((resolve) => - { - const image = new Image(); - - image.onload = () => resolve(image); - image.src = resolveURL(url); - }); - - Promise.all([ - loadXML('font.fnt'), - loadXML('font@0.5x.fnt'), - loadImage('font.png'), - loadImage('font@0.5x.png'), - loadImage('atlas.png'), - loadImage('atlas@0.5x.png'), - ]).then(([ - fontXML, - fontScaledXML, - fontImage, - fontScaledImage, - atlasImage, - atlasScaledImage, - ]) => - { - this.fontXML = fontXML; - this.fontScaledXML = fontScaledXML; - this.fontImage = fontImage; - this.fontScaledImage = fontScaledImage; - this.atlasImage = atlasImage; - this.atlasScaledImage = atlasScaledImage; - done(); - }); - }); - - it('should exist and return a function', function () - { - expect(bitmapFontParser).to.be.a('function'); - expect(bitmapFontParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not XML', function () - { - const spy = sinon.spy(); - const res = {}; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is not properly formatted XML', function () - { - const spy = sinon.spy(); - const res = { data: document.createDocumentFragment() }; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - // TODO: Test the texture cache code path. - // TODO: Test the loading texture code path. - // TODO: Test data-url code paths. - - it('should properly register bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charA.texture.frame.x).to.equal(2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charB.texture.frame.x).to.equal(2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charC.texture.frame.x).to.equal(23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charD.texture.frame.x).to.equal(19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register SCALED bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); - const font = BitmapText.registerFont(this.fontScaledXML, 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register bitmap font NESTED into spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); - - it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); - - spritesheet.resolution = 1; - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); -}); - -describe('PIXI.loaders.parseBitmapFontData', function () -{ - it('should exist', function () - { - expect(parseBitmapFontData).to.be.a('function'); - }); - - // TODO: Test the parser code. -}); diff --git a/packages/loaders/test/index.js b/packages/loaders/test/index.js index f00de5f..72c6554 100644 --- a/packages/loaders/test/index.js +++ b/packages/loaders/test/index.js @@ -1,4 +1,2 @@ -require('./bitmapFontParser'); require('./loader'); -require('./spritesheetParser'); require('./textureParser'); diff --git a/packages/loaders/test/resources/atlas.json b/packages/loaders/test/resources/atlas.json deleted file mode 100644 index 86e65a5..0000000 --- a/packages/loaders/test/resources/atlas.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas.png b/packages/loaders/test/resources/atlas.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/atlas@0.5x.json b/packages/loaders/test/resources/atlas@0.5x.json deleted file mode 100644 index ae990a1..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas@0.5x.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas@0.5x.png b/packages/loaders/test/resources/atlas@0.5x.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font.fnt b/packages/loaders/test/resources/font.fnt deleted file mode 100644 index 56e1060..0000000 --- a/packages/loaders/test/resources/font.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font.png b/packages/loaders/test/resources/font.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font@0.5x.fnt b/packages/loaders/test/resources/font@0.5x.fnt deleted file mode 100644 index 6c247c7..0000000 --- a/packages/loaders/test/resources/font@0.5x.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font@0.5x.png b/packages/loaders/test/resources/font@0.5x.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/spritesheetParser.js b/packages/loaders/test/spritesheetParser.js deleted file mode 100644 index 98890b8..0000000 --- a/packages/loaders/test/spritesheetParser.js +++ /dev/null @@ -1,213 +0,0 @@ -const path = require('path'); -const { spritesheetParser, Resource, Loader, getResourcePath } = require('../'); -const { Texture, BaseTexture } = require('@pixi/core'); - -describe('PIXI.loaders.spritesheetParser', function () -{ - it('should exist and return a function', function () - { - expect(spritesheetParser).to.be.a('function'); - expect(spritesheetParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not JSON', function () - { - const spy = sinon.spy(); - const res = {}; - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is JSON, but improper format', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, {}); - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should load the image & create textures if json is properly formatted', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); - const loader = new Loader(); - const addStub = sinon.stub(loader, 'add'); - const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); - - imgRes.texture = new Texture(new BaseTexture(imgRes.data)); - - addStub.yields(imgRes); - - spritesheetParser().call(loader, res, spy); - - addStub.restore(); - - expect(spy).to.have.been.calledOnce; - expect(addStub).to.have.been.calledWith( - `${res.name}_image`, - `${path.dirname(res.url)}/${res.data.meta.image}` - ); - expect(res).to.have.property('textures') - .that.is.an('object') - .with.keys(Object.keys(getJsonSpritesheet().frames)) - .and.has.property('0.png') - .that.is.an.instanceof(Texture); - }); - - it('should build the image url', function () - { - function getPath(url, image) - { - return getResourcePath({ - url, - data: { meta: { image } }, - }); - } - - let result = getPath('http://some.com/spritesheet.json', 'img.png'); - - expect(result).to.be.equals('http://some.com/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('http://some.com/some/img.png'); - - result = getPath('/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/img.png'); - - result = getPath('/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('/some/img.png'); - }); - - // TODO: Test that rectangles are created correctly. - // TODO: Test that bathc processing works correctly. - // TODO: Test that resolution processing works correctly. - // TODO: Test that metadata is honored. -}); - -function createMockResource(type, data) -{ - const name = `${Math.floor(Date.now() * Math.random())}`; - - return { - url: `http://localhost/doesnt_exist/${name}`, - name, - type, - data, - metadata: {}, - }; -} - -function getJsonSpritesheet() -{ - /* eslint-disable */ - return {"frames": { - "0.png": - { - "frame": {"x":14,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "1.png": - { - "frame": {"x":14,"y":42,"w":12,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, - "sourceSize": {"w":12,"h":14} - }, - "2.png": - { - "frame": {"x":14,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "3.png": - { - "frame": {"x":42,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "4.png": - { - "frame": {"x":28,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "5.png": - { - "frame": {"x":14,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "6.png": - { - "frame": {"x":0,"y":42,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "7.png": - { - "frame": {"x":0,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "8.png": - { - "frame": {"x":0,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "9.png": - { - "frame": {"x":0,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }}, - "meta": { - "app": "http://www.texturepacker.com", - "version": "1.0", - "image": "hud.png", - "format": "RGBA8888", - "size": {"w":64,"h":64}, - "scale": "1", - "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" - } - }; - /* eslint-enable */ -} diff --git a/packages/mixin-app-loader/LICENSE b/packages/mixin-app-loader/LICENSE new file mode 100644 index 0000000..5d16b50 --- /dev/null +++ b/packages/mixin-app-loader/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2013-2017 Mathew Groves, Chad Engler + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/packages/mixin-app-loader/README.md b/packages/mixin-app-loader/README.md new file mode 100644 index 0000000..3e89f86 --- /dev/null +++ b/packages/mixin-app-loader/README.md @@ -0,0 +1,13 @@ +# @pixi/mixin-app-loader + +## Installation + +```bash +npm install @pixi/mixin-app-loader +``` + +## Usage + +```js +import '@pixi/mixin-app-loader'; +``` \ No newline at end of file diff --git a/packages/mixin-app-loader/package.json b/packages/mixin-app-loader/package.json new file mode 100644 index 0000000..869ed4e --- /dev/null +++ b/packages/mixin-app-loader/package.json @@ -0,0 +1,41 @@ +{ + "name": "@pixi/mixin-app-loader", + "version": "5.0.0-alpha", + "main": "lib/mixin-app-loader.js", + "module": "lib/mixin-app-loader.es.js", + "description": "Support for loader in Application", + "author": "Mat Groves", + "contributors": [ + "Matt Karl " + ], + "homepage": "http://pixijs.com/", + "bugs": "https://github.com/pixijs/pixi.js/issues", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/pixijs/pixi.js.git" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "build:dev": "rollup -c", + "build": "rollup -cp", + "watch": "rollup -cw", + "postversion": "npm run build", + "test": "tester" + }, + "files": [ + "lib" + ], + "dependencies": { + "@pixi/app": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" + }, + "devDependencies": { + "@pixi/utils": "^5.0.0-alpha", + "@internal/tester": "^5.0.0-alpha", + "@internal/builder": "^5.0.0-alpha", + "rollup": "^0.50.0" + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/rollup.config.js b/packages/mixin-app-loader/rollup.config.js new file mode 100644 index 0000000..a73c874 --- /dev/null +++ b/packages/mixin-app-loader/rollup.config.js @@ -0,0 +1 @@ +export { default } from '@internal/builder'; diff --git a/packages/mixin-app-loader/src/index.js b/packages/mixin-app-loader/src/index.js new file mode 100644 index 0000000..354997a --- /dev/null +++ b/packages/mixin-app-loader/src/index.js @@ -0,0 +1,38 @@ +import { Application } from '@pixi/app'; +import { Loader, shared } from '@pixi/loaders'; + +Application.prototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperties(Application.prototype, { + loader: { + get() + { + if (!this._loader && this._options) + { + const { sharedLoader } = this._options; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +Application.prototype._parentDestroy = Application.prototype.destroy; +Application.prototype.destroy = function destroy(removeView) +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(removeView); +}; diff --git a/packages/mixin-app-loader/test/.eslintrc.json b/packages/mixin-app-loader/test/.eslintrc.json new file mode 100644 index 0000000..2094b04 --- /dev/null +++ b/packages/mixin-app-loader/test/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "globals": { + "expect": false, + "assert": false, + "sinon": false, + "PIXI": false + }, + "rules": { + "func-names": 0, + "no-unused-expressions": 0 + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/test/index.js b/packages/mixin-app-loader/test/index.js new file mode 100644 index 0000000..3468fe5 --- /dev/null +++ b/packages/mixin-app-loader/test/index.js @@ -0,0 +1,35 @@ +const { Application } = require('@pixi/app'); +const { Loader, shared } = require('@pixi/loaders'); +const { skipHello } = require('@pixi/utils'); + +require('../'); + +skipHello(); + +describe('PIXI.Application#loader', function () +{ + it('should contain loader property', function () + { + const obj = new Application(); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); + + it('should use sharedLoader option', function () + { + const obj = new Application({ sharedLoader: true }); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + expect(obj.loader).to.equal(shared); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); +}); diff --git a/packages/spritesheet/package.json b/packages/spritesheet/package.json index 34749e2..5f35442 100644 --- a/packages/spritesheet/package.json +++ b/packages/spritesheet/package.json @@ -31,7 +31,8 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/math": "^5.0.0-alpha", - "@pixi/utils": "^5.0.0-alpha" + "@pixi/utils": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" }, "devDependencies": { "@internal/builder": "^5.0.0-alpha", diff --git a/packages/spritesheet/src/SpritesheetLoader.js b/packages/spritesheet/src/SpritesheetLoader.js new file mode 100644 index 0000000..c7b34ea --- /dev/null +++ b/packages/spritesheet/src/SpritesheetLoader.js @@ -0,0 +1,83 @@ +import url from 'url'; +import { Resource, Loader } from '@pixi/loaders'; +import Spritesheet from './Spritesheet'; + +/** + * {@link PIXI.loaders.Loader Loader} middleware for loading + * texture atlases that have been created with TexturePacker or + * similar JSON-based spritesheet. This automatically generates + * Texture resources. + * @class + * @memberof PIXI + */ +export default class SpritesheetLoader +{ + /** + * Middleware function to support Spritesheets, this is automatically installed. + * @see PIXI.loaders.Loader.useMiddleware + */ + static middleware() + { + return function spritesheetLoader(resource, next) + { + const imageResourceName = `${resource.name}_image`; + + // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists + if (!resource.data + || resource.type !== Resource.TYPE.JSON + || !resource.data.frames + || this.resources[imageResourceName] + ) + { + next(); + + return; + } + + const loadOptions = { + crossOrigin: resource.crossOrigin, + loadType: Resource.LOAD_TYPE.IMAGE, + metadata: resource.metadata.imageMetadata, + parentResource: resource, + }; + + const resourcePath = SpritesheetLoader.getResourcePath(resource, this.baseUrl); + + // load the image for this sheet + this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) + { + const spritesheet = new Spritesheet( + res.texture.baseTexture, + resource.data, + resource.url + ); + + spritesheet.parse(() => + { + resource.spritesheet = spritesheet; + resource.textures = spritesheet.textures; + next(); + }); + }); + }; + } + + /** + * Get the spritesheets root path + * @param {PIXI.loader.Resource} resource - Resource to check path + * @param {string} baseUrl - Base root url + */ + static getResourcePath(resource, baseUrl) + { + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); + } +} + +// Install loader support for Spritesheet objects +Loader.useMiddleware(SpritesheetLoader.middleware); diff --git a/packages/spritesheet/src/index.js b/packages/spritesheet/src/index.js index 51d96a1..b9ee1e8 100644 --- a/packages/spritesheet/src/index.js +++ b/packages/spritesheet/src/index.js @@ -1 +1,2 @@ export { default as Spritesheet } from './Spritesheet'; +export { default as SpritesheetLoader } from './SpritesheetLoader'; diff --git a/packages/spritesheet/test/Spritesheet.js b/packages/spritesheet/test/Spritesheet.js new file mode 100644 index 0000000..35d8688 --- /dev/null +++ b/packages/spritesheet/test/Spritesheet.js @@ -0,0 +1,105 @@ +const { Spritesheet } = require('../'); +const { BaseTexture, Texture } = require('@pixi/core'); +const path = require('path'); + +describe('PIXI.Spritesheet', function () +{ + before(function () + { + this.resources = path.join(__dirname, 'resources'); + this.validate = function (spritesheet, done) + { + spritesheet.parse(function (textures) + { + const id = 'goldmine_10_5.png'; + const width = Math.floor(spritesheet.data.frames[id].frame.w); + const height = Math.floor(spritesheet.data.frames[id].frame.h); + + expect(Object.keys(textures).length).to.equal(1); + expect(Object.keys(spritesheet.textures).length).to.equal(1); + expect(textures[id]).to.be.an.instanceof(Texture); + expect(textures[id].width).to.equal(width / spritesheet.resolution); + expect(textures[id].height).to.equal(height / spritesheet.resolution); + expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); + spritesheet.destroy(true); + expect(spritesheet.textures).to.be.null; + expect(spritesheet.baseTexture).to.be.null; + done(); + }); + }; + }); + + it('should exist on PIXI', function () + { + expect(Spritesheet).to.be.a.function; + expect(Spritesheet.BATCH_SIZE).to.be.a.number; + }); + + it('should create an instance', function () + { + const baseTexture = new BaseTexture(); + const data = { + frames: {}, + meta: {}, + }; + + const spritesheet = new Spritesheet(baseTexture, data); + + expect(spritesheet.data).to.equal(data); + expect(spritesheet.baseTexture).to.equal(baseTexture); + expect(spritesheet.resolution).to.equal(1); + + spritesheet.destroy(true); + }); + + it('should create instance with scale resolution', function (done) + { + const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require + const image = new Image(); + + image.src = path.join(this.resources, data.meta.image); + image.onload = () => + { + const baseTexture = new BaseTexture(image, null, 1); + const spritesheet = new Spritesheet(baseTexture, data); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1.png'); + expect(spritesheet.resolution).to.equal(0.5); + this.validate(spritesheet, done); + }; + }); + + it('should create instance with BaseTexture source scale', function (done) + { + const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require + const baseTexture = new BaseTexture.fromImage(data.meta.image, undefined, undefined, 1.5); + const spritesheet = new Spritesheet(baseTexture, data); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1.png'); + expect(spritesheet.resolution).to.equal(0.5); + + this.validate(spritesheet, done); + }); + + it('should create instance with filename resolution', function (done) + { + const uri = path.resolve(this.resources, 'building1@2x.json'); + const data = require(uri); // eslint-disable-line global-require + const image = new Image(); + + image.src = path.join(this.resources, data.meta.image); + image.onload = () => + { + const baseTexture = new BaseTexture(image, null, 1); + const spritesheet = new Spritesheet(baseTexture, data, uri); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1@2x.png'); + expect(spritesheet.resolution).to.equal(2); + + this.validate(spritesheet, done); + }; + }); +}); diff --git a/packages/spritesheet/test/SpritesheetLoader.js b/packages/spritesheet/test/SpritesheetLoader.js new file mode 100644 index 0000000..e4205ec --- /dev/null +++ b/packages/spritesheet/test/SpritesheetLoader.js @@ -0,0 +1,214 @@ +const path = require('path'); +const { Loader, Resource } = require('@pixi/loaders'); +const { Texture, BaseTexture } = require('@pixi/core'); +const { SpritesheetLoader } = require('../'); + +describe('PIXI.SpritesheetLoader', function () +{ + it('should exist and return a function', function () + { + expect(SpritesheetLoader.middleware).to.be.a('function'); + expect(SpritesheetLoader.middleware()).to.be.a('function'); + }); + + it('should do nothing if the resource is not JSON', function () + { + const spy = sinon.spy(); + const res = {}; + + SpritesheetLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should do nothing if the resource is JSON, but improper format', function () + { + const spy = sinon.spy(); + const res = createMockResource(Resource.TYPE.JSON, {}); + + SpritesheetLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should load the image & create textures if json is properly formatted', function () + { + const spy = sinon.spy(); + const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); + const loader = new Loader(); + const addStub = sinon.stub(loader, 'add'); + const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); + + imgRes.texture = new Texture(new BaseTexture(imgRes.data)); + + addStub.yields(imgRes); + + SpritesheetLoader.middleware().call(loader, res, spy); + + addStub.restore(); + + expect(spy).to.have.been.calledOnce; + expect(addStub).to.have.been.calledWith( + `${res.name}_image`, + `${path.dirname(res.url)}/${res.data.meta.image}` + ); + expect(res).to.have.property('textures') + .that.is.an('object') + .with.keys(Object.keys(getJsonSpritesheet().frames)) + .and.has.property('0.png') + .that.is.an.instanceof(Texture); + }); + + it('should build the image url', function () + { + function getPath(url, image) + { + return SpritesheetLoader.getResourcePath({ + url, + data: { meta: { image } }, + }); + } + + let result = getPath('http://some.com/spritesheet.json', 'img.png'); + + expect(result).to.be.equals('http://some.com/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); + expect(result).to.be.equals('http://some.com/some/dir/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); + expect(result).to.be.equals('http://some.com/some/dir/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); + expect(result).to.be.equals('http://some.com/some/img.png'); + + result = getPath('/spritesheet.json', 'img.png'); + expect(result).to.be.equals('/img.png'); + + result = getPath('/some/dir/spritesheet.json', 'img.png'); + expect(result).to.be.equals('/some/dir/img.png'); + + result = getPath('/some/dir/spritesheet.json', './img.png'); + expect(result).to.be.equals('/some/dir/img.png'); + + result = getPath('/some/dir/spritesheet.json', '../img.png'); + expect(result).to.be.equals('/some/img.png'); + }); + + // TODO: Test that rectangles are created correctly. + // TODO: Test that bathc processing works correctly. + // TODO: Test that resolution processing works correctly. + // TODO: Test that metadata is honored. +}); + +function createMockResource(type, data) +{ + const name = `${Math.floor(Date.now() * Math.random())}`; + + return { + url: `http://localhost/doesnt_exist/${name}`, + name, + type, + data, + metadata: {}, + }; +} + +function getJsonSpritesheet() +{ + /* eslint-disable */ + return {"frames": { + "0.png": + { + "frame": {"x":14,"y":28,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "1.png": + { + "frame": {"x":14,"y":42,"w":12,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, + "sourceSize": {"w":12,"h":14} + }, + "2.png": + { + "frame": {"x":14,"y":14,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "3.png": + { + "frame": {"x":42,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "4.png": + { + "frame": {"x":28,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "5.png": + { + "frame": {"x":14,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "6.png": + { + "frame": {"x":0,"y":42,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "7.png": + { + "frame": {"x":0,"y":28,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "8.png": + { + "frame": {"x":0,"y":14,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "9.png": + { + "frame": {"x":0,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }}, + "meta": { + "app": "http://www.texturepacker.com", + "version": "1.0", + "image": "hud.png", + "format": "RGBA8888", + "size": {"w":64,"h":64}, + "scale": "1", + "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" + } + }; + /* eslint-enable */ +} diff --git a/packages/spritesheet/test/index.js b/packages/spritesheet/test/index.js index 35d8688..8cf5636 100644 --- a/packages/spritesheet/test/index.js +++ b/packages/spritesheet/test/index.js @@ -1,105 +1,2 @@ -const { Spritesheet } = require('../'); -const { BaseTexture, Texture } = require('@pixi/core'); -const path = require('path'); - -describe('PIXI.Spritesheet', function () -{ - before(function () - { - this.resources = path.join(__dirname, 'resources'); - this.validate = function (spritesheet, done) - { - spritesheet.parse(function (textures) - { - const id = 'goldmine_10_5.png'; - const width = Math.floor(spritesheet.data.frames[id].frame.w); - const height = Math.floor(spritesheet.data.frames[id].frame.h); - - expect(Object.keys(textures).length).to.equal(1); - expect(Object.keys(spritesheet.textures).length).to.equal(1); - expect(textures[id]).to.be.an.instanceof(Texture); - expect(textures[id].width).to.equal(width / spritesheet.resolution); - expect(textures[id].height).to.equal(height / spritesheet.resolution); - expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); - spritesheet.destroy(true); - expect(spritesheet.textures).to.be.null; - expect(spritesheet.baseTexture).to.be.null; - done(); - }); - }; - }); - - it('should exist on PIXI', function () - { - expect(Spritesheet).to.be.a.function; - expect(Spritesheet.BATCH_SIZE).to.be.a.number; - }); - - it('should create an instance', function () - { - const baseTexture = new BaseTexture(); - const data = { - frames: {}, - meta: {}, - }; - - const spritesheet = new Spritesheet(baseTexture, data); - - expect(spritesheet.data).to.equal(data); - expect(spritesheet.baseTexture).to.equal(baseTexture); - expect(spritesheet.resolution).to.equal(1); - - spritesheet.destroy(true); - }); - - it('should create instance with scale resolution', function (done) - { - const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require - const image = new Image(); - - image.src = path.join(this.resources, data.meta.image); - image.onload = () => - { - const baseTexture = new BaseTexture(image, null, 1); - const spritesheet = new Spritesheet(baseTexture, data); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1.png'); - expect(spritesheet.resolution).to.equal(0.5); - this.validate(spritesheet, done); - }; - }); - - it('should create instance with BaseTexture source scale', function (done) - { - const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require - const baseTexture = new BaseTexture.fromImage(data.meta.image, undefined, undefined, 1.5); - const spritesheet = new Spritesheet(baseTexture, data); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1.png'); - expect(spritesheet.resolution).to.equal(0.5); - - this.validate(spritesheet, done); - }); - - it('should create instance with filename resolution', function (done) - { - const uri = path.resolve(this.resources, 'building1@2x.json'); - const data = require(uri); // eslint-disable-line global-require - const image = new Image(); - - image.src = path.join(this.resources, data.meta.image); - image.onload = () => - { - const baseTexture = new BaseTexture(image, null, 1); - const spritesheet = new Spritesheet(baseTexture, data, uri); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1@2x.png'); - expect(spritesheet.resolution).to.equal(2); - - this.validate(spritesheet, done); - }; - }); -}); +require('./Spritesheet'); +require('./SpritesheetLoader'); diff --git a/packages/text-bitmap/package.json b/packages/text-bitmap/package.json index a8a5f95..4b47da6 100644 --- a/packages/text-bitmap/package.json +++ b/packages/text-bitmap/package.json @@ -23,7 +23,7 @@ "build": "rollup -cp", "watch": "rollup -cw", "postversion": "npm run build", - "test": "exit 0" + "test": "tester" }, "files": [ "lib" @@ -31,12 +31,15 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha", "@pixi/sprite": "^5.0.0-alpha", "@pixi/settings": "^5.0.0-alpha", "@pixi/math": "^5.0.0-alpha", "@pixi/display": "^5.0.0-alpha" }, "devDependencies": { + "@pixi/spritesheet": "^5.0.0-alpha", + "@internal/tester": "^5.0.0-alpha", "@internal/builder": "^5.0.0-alpha", "rollup": "^0.50.0" } diff --git a/packages/text-bitmap/src/BitmapFontLoader.js b/packages/text-bitmap/src/BitmapFontLoader.js new file mode 100644 index 0000000..3956060 --- /dev/null +++ b/packages/text-bitmap/src/BitmapFontLoader.js @@ -0,0 +1,112 @@ +import * as path from 'path'; +import { TextureCache } from '@pixi/utils'; +import { Resource, Loader } from '@pixi/loaders'; +import BitmapText from './BitmapText'; + +/** + * {@link PIXI.loaders.Loader Loader} middleware for loading + * bitmap-based fonts suitable for using with {@link PIXI.BitmapText}. + * @class + * @memberof PIXI + */ +export default class BitmapFontLoader +{ + /** + * Register a BitmapText font from loader resource. + * + * @param {PIXI.loaders.Resource} resource - Loader resource. + * @param {PIXI.Texture} texture - Reference to texture. + */ + static parse(resource, texture) + { + resource.bitmapFont = BitmapText.registerFont(resource.data, texture); + } + + /** + * Middleware function to support BitmapText fonts, this is automatically installed. + * @see PIXI.loaders.Loader.useMiddleware + */ + static middleware() + { + return function bitmapFontLoader(resource, next) + { + // skip if no data or not xml data + if (!resource.data || resource.type !== Resource.TYPE.XML) + { + next(); + + return; + } + + // skip if not bitmap font data, using some silly duck-typing + if (resource.data.getElementsByTagName('page').length === 0 + || resource.data.getElementsByTagName('info').length === 0 + || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null + ) + { + next(); + + return; + } + + let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; + + if (resource.isDataUrl) + { + if (xmlUrl === '.') + { + xmlUrl = ''; + } + + if (this.baseUrl && xmlUrl) + { + // if baseurl has a trailing slash then add one to xmlUrl so the replace works below + if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') + { + xmlUrl += '/'; + } + } + } + + // remove baseUrl from xmlUrl + xmlUrl = xmlUrl.replace(this.baseUrl, ''); + + // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. + if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') + { + xmlUrl += '/'; + } + + const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); + + if (TextureCache[textureUrl]) + { + // reuse existing texture + BitmapFontLoader.parse(resource, TextureCache[textureUrl]); + next(); + } + else + { + const loadOptions = { + crossOrigin: resource.crossOrigin, + loadType: Resource.LOAD_TYPE.IMAGE, + metadata: resource.metadata.imageMetadata, + parentResource: resource, + }; + + // load the texture for the font + this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => + { + BitmapFontLoader.parse(resource, res.texture); + next(); + }); + } + }; + } +} + +// Add custom add support for loading fnt files as XML +Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); + +// Install loader support for BitmapText +Loader.useMiddleware(BitmapFontLoader.middleware); diff --git a/packages/text-bitmap/src/index.js b/packages/text-bitmap/src/index.js index a98e7b4..e5afd2c 100644 --- a/packages/text-bitmap/src/index.js +++ b/packages/text-bitmap/src/index.js @@ -1 +1,2 @@ export { default as BitmapText } from './BitmapText'; +export { default as BitmapFontLoader } from './BitmapFontLoader'; diff --git a/packages/text-bitmap/test/.eslintrc.json b/packages/text-bitmap/test/.eslintrc.json new file mode 100644 index 0000000..2094b04 --- /dev/null +++ b/packages/text-bitmap/test/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "globals": { + "expect": false, + "assert": false, + "sinon": false, + "PIXI": false + }, + "rules": { + "func-names": 0, + "no-unused-expressions": 0 + } +} \ No newline at end of file diff --git a/packages/text-bitmap/test/BitmapFontLoader.js b/packages/text-bitmap/test/BitmapFontLoader.js new file mode 100644 index 0000000..a76fc1c --- /dev/null +++ b/packages/text-bitmap/test/BitmapFontLoader.js @@ -0,0 +1,319 @@ +const path = require('path'); +const fs = require('fs'); +const { BaseTextureCache, TextureCache } = require('@pixi/utils'); +const { Texture, BaseTexture } = require('@pixi/core'); +const { Spritesheet } = require('@pixi/spritesheet'); +const { BitmapText, BitmapFontLoader } = require('../'); + +describe('PIXI.BitmapFontLoader', function () +{ + afterEach(function () + { + for (const font in BitmapText.fonts) + { + delete BitmapText.fonts[font]; + } + for (const baseTexture in BaseTextureCache) + { + delete BaseTextureCache[baseTexture]; + } + for (const texture in TextureCache) + { + delete TextureCache[texture]; + } + }); + + before(function (done) + { + const resolveURL = (url) => path.resolve(this.resources, url); + + this.resources = path.join(__dirname, 'resources'); + this.fontXML = null; + this.fontScaledXML = null; + this.fontImage = null; + this.fontScaledImage = null; + this.atlasImage = null; + this.atlasScaledImage = null; + this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require + this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require + + const loadXML = (url) => new Promise((resolve) => + fs.readFile(resolveURL(url), 'utf8', (err, data) => + { + expect(err).to.be.null; + resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); + })); + + const loadImage = (url) => new Promise((resolve) => + { + const image = new Image(); + + image.onload = () => resolve(image); + image.src = resolveURL(url); + }); + + Promise.all([ + loadXML('font.fnt'), + loadXML('font@0.5x.fnt'), + loadImage('font.png'), + loadImage('font@0.5x.png'), + loadImage('atlas.png'), + loadImage('atlas@0.5x.png'), + ]).then(([ + fontXML, + fontScaledXML, + fontImage, + fontScaledImage, + atlasImage, + atlasScaledImage, + ]) => + { + this.fontXML = fontXML; + this.fontScaledXML = fontScaledXML; + this.fontImage = fontImage; + this.fontScaledImage = fontScaledImage; + this.atlasImage = atlasImage; + this.atlasScaledImage = atlasScaledImage; + done(); + }); + }); + + it('should exist and return a function', function () + { + expect(BitmapFontLoader.middleware).to.be.a('function'); + expect(BitmapFontLoader.middleware()).to.be.a('function'); + }); + + it('should do nothing if the resource is not XML', function () + { + const spy = sinon.spy(); + const res = {}; + + BitmapFontLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should do nothing if the resource is not properly formatted XML', function () + { + const spy = sinon.spy(); + const res = { data: document.createDocumentFragment() }; + + BitmapFontLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + // TODO: Test the texture cache code path. + // TODO: Test the loading texture code path. + // TODO: Test data-url code paths. + + it('should properly register bitmap font', function (done) + { + const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); + 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charA.texture.frame.x).to.equal(2); + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charB.texture.frame.x).to.equal(2); + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charC.texture.frame.x).to.equal(23); + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charD.texture.frame.x).to.equal(19); + 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]; + + expect(charE).to.be.undefined; + done(); + }); + + it('should properly register SCALED bitmap font', function (done) + { + const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); + const font = BitmapText.registerFont(this.fontScaledXML, 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 + 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]; + + expect(charE).to.be.undefined; + done(); + }); + + it('should properly register bitmap font NESTED into spritesheet', function (done) + { + const baseTexture = new BaseTexture(this.atlasImage, null, 1); + const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); + + spritesheet.parse(() => + { + const fontTexture = Texture.fromFrame('resources/font.png'); + const font = BitmapText.registerFont(this.fontXML, fontTexture); + const fontX = 158; // bare value from spritesheet frame + const fontY = 2; // bare value from spritesheet frame + + 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charA.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charB.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charC.texture.frame.x).to.equal(fontX + 23); + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charD.texture.frame.x).to.equal(fontX + 19); + 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]; + + expect(charE).to.be.undefined; + done(); + }); + }); + + it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) + { + const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); + const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); + + spritesheet.resolution = 1; + + spritesheet.parse(() => + { + const fontTexture = Texture.fromFrame('resources/font.png'); + const font = BitmapText.registerFont(this.fontXML, fontTexture); + const fontX = 158; // bare value from spritesheet frame + const fontY = 2; // bare value from spritesheet frame + + 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charA.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charB.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charC.texture.frame.x).to.equal(fontX + 23); + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charD.texture.frame.x).to.equal(fontX + 19); + 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]; + + expect(charE).to.be.undefined; + done(); + }); + }); + + it('should parse exist', function () + { + expect(BitmapFontLoader.parse).to.be.a('function'); + }); +}); diff --git a/packages/text-bitmap/test/index.js b/packages/text-bitmap/test/index.js new file mode 100644 index 0000000..688fee2 --- /dev/null +++ b/packages/text-bitmap/test/index.js @@ -0,0 +1 @@ +require('./BitmapFontLoader'); diff --git a/packages/text-bitmap/test/resources/atlas.json b/packages/text-bitmap/test/resources/atlas.json new file mode 100644 index 0000000..86e65a5 --- /dev/null +++ b/packages/text-bitmap/test/resources/atlas.json @@ -0,0 +1,25 @@ +{ + "meta": { + "image": "atlas.png", + "size": {"w":256,"h":256}, + "scale": "1" + }, + "frames": { + "resources/test.png": + { + "frame": {"x":2,"y":2,"w":152,"h":188}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, + "sourceSize": {"w":300,"h":225} + }, + "resources/font.png": + { + "frame": {"x":158,"y":2,"w":40,"h":42}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, + "sourceSize": {"w":43,"h":46} + } + } +} \ No newline at end of file diff --git a/packages/text-bitmap/test/resources/atlas.png b/packages/text-bitmap/test/resources/atlas.png new file mode 100644 index 0000000..d5e7892 --- /dev/null +++ b/packages/text-bitmap/test/resources/atlas.png Binary files differ diff --git a/packages/text-bitmap/test/resources/atlas@0.5x.json b/packages/text-bitmap/test/resources/atlas@0.5x.json new file mode 100644 index 0000000..ae990a1 --- /dev/null +++ b/packages/text-bitmap/test/resources/atlas@0.5x.json @@ -0,0 +1,25 @@ +{ + "meta": { + "image": "atlas@0.5x.png", + "size": {"w":256,"h":256}, + "scale": "1" + }, + "frames": { + "resources/test.png": + { + "frame": {"x":2,"y":2,"w":152,"h":188}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, + "sourceSize": {"w":300,"h":225} + }, + "resources/font.png": + { + "frame": {"x":158,"y":2,"w":40,"h":42}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, + "sourceSize": {"w":43,"h":46} + } + } +} \ No newline at end of file diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/packages/loaders/src/spritesheetParser.js b/packages/loaders/src/spritesheetParser.js deleted file mode 100644 index f7fa206..0000000 --- a/packages/loaders/src/spritesheetParser.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Resource } from 'resource-loader'; -import url from 'url'; -import { Spritesheet } from '@pixi/spritesheet'; - -export default function () -{ - return function spritesheetParser(resource, next) - { - const imageResourceName = `${resource.name}_image`; - - // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists - if (!resource.data - || resource.type !== Resource.TYPE.JSON - || !resource.data.frames - || this.resources[imageResourceName] - ) - { - next(); - - return; - } - - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - const resourcePath = getResourcePath(resource, this.baseUrl); - - // load the image for this sheet - this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) - { - const spritesheet = new Spritesheet( - res.texture.baseTexture, - resource.data, - resource.url - ); - - spritesheet.parse(() => - { - resource.spritesheet = spritesheet; - resource.textures = spritesheet.textures; - next(); - }); - }); - }; -} - -export function getResourcePath(resource, baseUrl) -{ - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - return resource.data.meta.image; - } - - return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); -} diff --git a/packages/loaders/test/bitmapFontParser.js b/packages/loaders/test/bitmapFontParser.js deleted file mode 100644 index 43ef194..0000000 --- a/packages/loaders/test/bitmapFontParser.js +++ /dev/null @@ -1,325 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const { BitmapText } = require('@pixi/text-bitmap'); -const { BaseTextureCache, TextureCache } = require('@pixi/utils'); -const { Texture, BaseTexture } = require('@pixi/core'); -const { Spritesheet } = require('@pixi/spritesheet'); -const { bitmapFontParser, parseBitmapFontData } = require('../'); - -describe('PIXI.loaders.bitmapFontParser', function () -{ - afterEach(function () - { - for (const font in BitmapText.fonts) - { - delete BitmapText.fonts[font]; - } - for (const baseTexture in BaseTextureCache) - { - delete BaseTextureCache[baseTexture]; - } - for (const texture in TextureCache) - { - delete TextureCache[texture]; - } - }); - - before(function (done) - { - const resolveURL = (url) => path.resolve(this.resources, url); - - this.resources = path.join(__dirname, 'resources'); - this.fontXML = null; - this.fontScaledXML = null; - this.fontImage = null; - this.fontScaledImage = null; - this.atlasImage = null; - this.atlasScaledImage = null; - this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require - this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require - - const loadXML = (url) => new Promise((resolve) => - fs.readFile(resolveURL(url), 'utf8', (err, data) => - { - expect(err).to.be.null; - resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); - })); - - const loadImage = (url) => new Promise((resolve) => - { - const image = new Image(); - - image.onload = () => resolve(image); - image.src = resolveURL(url); - }); - - Promise.all([ - loadXML('font.fnt'), - loadXML('font@0.5x.fnt'), - loadImage('font.png'), - loadImage('font@0.5x.png'), - loadImage('atlas.png'), - loadImage('atlas@0.5x.png'), - ]).then(([ - fontXML, - fontScaledXML, - fontImage, - fontScaledImage, - atlasImage, - atlasScaledImage, - ]) => - { - this.fontXML = fontXML; - this.fontScaledXML = fontScaledXML; - this.fontImage = fontImage; - this.fontScaledImage = fontScaledImage; - this.atlasImage = atlasImage; - this.atlasScaledImage = atlasScaledImage; - done(); - }); - }); - - it('should exist and return a function', function () - { - expect(bitmapFontParser).to.be.a('function'); - expect(bitmapFontParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not XML', function () - { - const spy = sinon.spy(); - const res = {}; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is not properly formatted XML', function () - { - const spy = sinon.spy(); - const res = { data: document.createDocumentFragment() }; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - // TODO: Test the texture cache code path. - // TODO: Test the loading texture code path. - // TODO: Test data-url code paths. - - it('should properly register bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charA.texture.frame.x).to.equal(2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charB.texture.frame.x).to.equal(2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charC.texture.frame.x).to.equal(23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charD.texture.frame.x).to.equal(19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register SCALED bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); - const font = BitmapText.registerFont(this.fontScaledXML, 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register bitmap font NESTED into spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); - - it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); - - spritesheet.resolution = 1; - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); -}); - -describe('PIXI.loaders.parseBitmapFontData', function () -{ - it('should exist', function () - { - expect(parseBitmapFontData).to.be.a('function'); - }); - - // TODO: Test the parser code. -}); diff --git a/packages/loaders/test/index.js b/packages/loaders/test/index.js index f00de5f..72c6554 100644 --- a/packages/loaders/test/index.js +++ b/packages/loaders/test/index.js @@ -1,4 +1,2 @@ -require('./bitmapFontParser'); require('./loader'); -require('./spritesheetParser'); require('./textureParser'); diff --git a/packages/loaders/test/resources/atlas.json b/packages/loaders/test/resources/atlas.json deleted file mode 100644 index 86e65a5..0000000 --- a/packages/loaders/test/resources/atlas.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas.png b/packages/loaders/test/resources/atlas.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/atlas@0.5x.json b/packages/loaders/test/resources/atlas@0.5x.json deleted file mode 100644 index ae990a1..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas@0.5x.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas@0.5x.png b/packages/loaders/test/resources/atlas@0.5x.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font.fnt b/packages/loaders/test/resources/font.fnt deleted file mode 100644 index 56e1060..0000000 --- a/packages/loaders/test/resources/font.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font.png b/packages/loaders/test/resources/font.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font@0.5x.fnt b/packages/loaders/test/resources/font@0.5x.fnt deleted file mode 100644 index 6c247c7..0000000 --- a/packages/loaders/test/resources/font@0.5x.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font@0.5x.png b/packages/loaders/test/resources/font@0.5x.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/spritesheetParser.js b/packages/loaders/test/spritesheetParser.js deleted file mode 100644 index 98890b8..0000000 --- a/packages/loaders/test/spritesheetParser.js +++ /dev/null @@ -1,213 +0,0 @@ -const path = require('path'); -const { spritesheetParser, Resource, Loader, getResourcePath } = require('../'); -const { Texture, BaseTexture } = require('@pixi/core'); - -describe('PIXI.loaders.spritesheetParser', function () -{ - it('should exist and return a function', function () - { - expect(spritesheetParser).to.be.a('function'); - expect(spritesheetParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not JSON', function () - { - const spy = sinon.spy(); - const res = {}; - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is JSON, but improper format', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, {}); - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should load the image & create textures if json is properly formatted', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); - const loader = new Loader(); - const addStub = sinon.stub(loader, 'add'); - const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); - - imgRes.texture = new Texture(new BaseTexture(imgRes.data)); - - addStub.yields(imgRes); - - spritesheetParser().call(loader, res, spy); - - addStub.restore(); - - expect(spy).to.have.been.calledOnce; - expect(addStub).to.have.been.calledWith( - `${res.name}_image`, - `${path.dirname(res.url)}/${res.data.meta.image}` - ); - expect(res).to.have.property('textures') - .that.is.an('object') - .with.keys(Object.keys(getJsonSpritesheet().frames)) - .and.has.property('0.png') - .that.is.an.instanceof(Texture); - }); - - it('should build the image url', function () - { - function getPath(url, image) - { - return getResourcePath({ - url, - data: { meta: { image } }, - }); - } - - let result = getPath('http://some.com/spritesheet.json', 'img.png'); - - expect(result).to.be.equals('http://some.com/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('http://some.com/some/img.png'); - - result = getPath('/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/img.png'); - - result = getPath('/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('/some/img.png'); - }); - - // TODO: Test that rectangles are created correctly. - // TODO: Test that bathc processing works correctly. - // TODO: Test that resolution processing works correctly. - // TODO: Test that metadata is honored. -}); - -function createMockResource(type, data) -{ - const name = `${Math.floor(Date.now() * Math.random())}`; - - return { - url: `http://localhost/doesnt_exist/${name}`, - name, - type, - data, - metadata: {}, - }; -} - -function getJsonSpritesheet() -{ - /* eslint-disable */ - return {"frames": { - "0.png": - { - "frame": {"x":14,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "1.png": - { - "frame": {"x":14,"y":42,"w":12,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, - "sourceSize": {"w":12,"h":14} - }, - "2.png": - { - "frame": {"x":14,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "3.png": - { - "frame": {"x":42,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "4.png": - { - "frame": {"x":28,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "5.png": - { - "frame": {"x":14,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "6.png": - { - "frame": {"x":0,"y":42,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "7.png": - { - "frame": {"x":0,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "8.png": - { - "frame": {"x":0,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "9.png": - { - "frame": {"x":0,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }}, - "meta": { - "app": "http://www.texturepacker.com", - "version": "1.0", - "image": "hud.png", - "format": "RGBA8888", - "size": {"w":64,"h":64}, - "scale": "1", - "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" - } - }; - /* eslint-enable */ -} diff --git a/packages/mixin-app-loader/LICENSE b/packages/mixin-app-loader/LICENSE new file mode 100644 index 0000000..5d16b50 --- /dev/null +++ b/packages/mixin-app-loader/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2013-2017 Mathew Groves, Chad Engler + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/packages/mixin-app-loader/README.md b/packages/mixin-app-loader/README.md new file mode 100644 index 0000000..3e89f86 --- /dev/null +++ b/packages/mixin-app-loader/README.md @@ -0,0 +1,13 @@ +# @pixi/mixin-app-loader + +## Installation + +```bash +npm install @pixi/mixin-app-loader +``` + +## Usage + +```js +import '@pixi/mixin-app-loader'; +``` \ No newline at end of file diff --git a/packages/mixin-app-loader/package.json b/packages/mixin-app-loader/package.json new file mode 100644 index 0000000..869ed4e --- /dev/null +++ b/packages/mixin-app-loader/package.json @@ -0,0 +1,41 @@ +{ + "name": "@pixi/mixin-app-loader", + "version": "5.0.0-alpha", + "main": "lib/mixin-app-loader.js", + "module": "lib/mixin-app-loader.es.js", + "description": "Support for loader in Application", + "author": "Mat Groves", + "contributors": [ + "Matt Karl " + ], + "homepage": "http://pixijs.com/", + "bugs": "https://github.com/pixijs/pixi.js/issues", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/pixijs/pixi.js.git" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "build:dev": "rollup -c", + "build": "rollup -cp", + "watch": "rollup -cw", + "postversion": "npm run build", + "test": "tester" + }, + "files": [ + "lib" + ], + "dependencies": { + "@pixi/app": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" + }, + "devDependencies": { + "@pixi/utils": "^5.0.0-alpha", + "@internal/tester": "^5.0.0-alpha", + "@internal/builder": "^5.0.0-alpha", + "rollup": "^0.50.0" + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/rollup.config.js b/packages/mixin-app-loader/rollup.config.js new file mode 100644 index 0000000..a73c874 --- /dev/null +++ b/packages/mixin-app-loader/rollup.config.js @@ -0,0 +1 @@ +export { default } from '@internal/builder'; diff --git a/packages/mixin-app-loader/src/index.js b/packages/mixin-app-loader/src/index.js new file mode 100644 index 0000000..354997a --- /dev/null +++ b/packages/mixin-app-loader/src/index.js @@ -0,0 +1,38 @@ +import { Application } from '@pixi/app'; +import { Loader, shared } from '@pixi/loaders'; + +Application.prototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperties(Application.prototype, { + loader: { + get() + { + if (!this._loader && this._options) + { + const { sharedLoader } = this._options; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +Application.prototype._parentDestroy = Application.prototype.destroy; +Application.prototype.destroy = function destroy(removeView) +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(removeView); +}; diff --git a/packages/mixin-app-loader/test/.eslintrc.json b/packages/mixin-app-loader/test/.eslintrc.json new file mode 100644 index 0000000..2094b04 --- /dev/null +++ b/packages/mixin-app-loader/test/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "globals": { + "expect": false, + "assert": false, + "sinon": false, + "PIXI": false + }, + "rules": { + "func-names": 0, + "no-unused-expressions": 0 + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/test/index.js b/packages/mixin-app-loader/test/index.js new file mode 100644 index 0000000..3468fe5 --- /dev/null +++ b/packages/mixin-app-loader/test/index.js @@ -0,0 +1,35 @@ +const { Application } = require('@pixi/app'); +const { Loader, shared } = require('@pixi/loaders'); +const { skipHello } = require('@pixi/utils'); + +require('../'); + +skipHello(); + +describe('PIXI.Application#loader', function () +{ + it('should contain loader property', function () + { + const obj = new Application(); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); + + it('should use sharedLoader option', function () + { + const obj = new Application({ sharedLoader: true }); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + expect(obj.loader).to.equal(shared); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); +}); diff --git a/packages/spritesheet/package.json b/packages/spritesheet/package.json index 34749e2..5f35442 100644 --- a/packages/spritesheet/package.json +++ b/packages/spritesheet/package.json @@ -31,7 +31,8 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/math": "^5.0.0-alpha", - "@pixi/utils": "^5.0.0-alpha" + "@pixi/utils": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" }, "devDependencies": { "@internal/builder": "^5.0.0-alpha", diff --git a/packages/spritesheet/src/SpritesheetLoader.js b/packages/spritesheet/src/SpritesheetLoader.js new file mode 100644 index 0000000..c7b34ea --- /dev/null +++ b/packages/spritesheet/src/SpritesheetLoader.js @@ -0,0 +1,83 @@ +import url from 'url'; +import { Resource, Loader } from '@pixi/loaders'; +import Spritesheet from './Spritesheet'; + +/** + * {@link PIXI.loaders.Loader Loader} middleware for loading + * texture atlases that have been created with TexturePacker or + * similar JSON-based spritesheet. This automatically generates + * Texture resources. + * @class + * @memberof PIXI + */ +export default class SpritesheetLoader +{ + /** + * Middleware function to support Spritesheets, this is automatically installed. + * @see PIXI.loaders.Loader.useMiddleware + */ + static middleware() + { + return function spritesheetLoader(resource, next) + { + const imageResourceName = `${resource.name}_image`; + + // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists + if (!resource.data + || resource.type !== Resource.TYPE.JSON + || !resource.data.frames + || this.resources[imageResourceName] + ) + { + next(); + + return; + } + + const loadOptions = { + crossOrigin: resource.crossOrigin, + loadType: Resource.LOAD_TYPE.IMAGE, + metadata: resource.metadata.imageMetadata, + parentResource: resource, + }; + + const resourcePath = SpritesheetLoader.getResourcePath(resource, this.baseUrl); + + // load the image for this sheet + this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) + { + const spritesheet = new Spritesheet( + res.texture.baseTexture, + resource.data, + resource.url + ); + + spritesheet.parse(() => + { + resource.spritesheet = spritesheet; + resource.textures = spritesheet.textures; + next(); + }); + }); + }; + } + + /** + * Get the spritesheets root path + * @param {PIXI.loader.Resource} resource - Resource to check path + * @param {string} baseUrl - Base root url + */ + static getResourcePath(resource, baseUrl) + { + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); + } +} + +// Install loader support for Spritesheet objects +Loader.useMiddleware(SpritesheetLoader.middleware); diff --git a/packages/spritesheet/src/index.js b/packages/spritesheet/src/index.js index 51d96a1..b9ee1e8 100644 --- a/packages/spritesheet/src/index.js +++ b/packages/spritesheet/src/index.js @@ -1 +1,2 @@ export { default as Spritesheet } from './Spritesheet'; +export { default as SpritesheetLoader } from './SpritesheetLoader'; diff --git a/packages/spritesheet/test/Spritesheet.js b/packages/spritesheet/test/Spritesheet.js new file mode 100644 index 0000000..35d8688 --- /dev/null +++ b/packages/spritesheet/test/Spritesheet.js @@ -0,0 +1,105 @@ +const { Spritesheet } = require('../'); +const { BaseTexture, Texture } = require('@pixi/core'); +const path = require('path'); + +describe('PIXI.Spritesheet', function () +{ + before(function () + { + this.resources = path.join(__dirname, 'resources'); + this.validate = function (spritesheet, done) + { + spritesheet.parse(function (textures) + { + const id = 'goldmine_10_5.png'; + const width = Math.floor(spritesheet.data.frames[id].frame.w); + const height = Math.floor(spritesheet.data.frames[id].frame.h); + + expect(Object.keys(textures).length).to.equal(1); + expect(Object.keys(spritesheet.textures).length).to.equal(1); + expect(textures[id]).to.be.an.instanceof(Texture); + expect(textures[id].width).to.equal(width / spritesheet.resolution); + expect(textures[id].height).to.equal(height / spritesheet.resolution); + expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); + spritesheet.destroy(true); + expect(spritesheet.textures).to.be.null; + expect(spritesheet.baseTexture).to.be.null; + done(); + }); + }; + }); + + it('should exist on PIXI', function () + { + expect(Spritesheet).to.be.a.function; + expect(Spritesheet.BATCH_SIZE).to.be.a.number; + }); + + it('should create an instance', function () + { + const baseTexture = new BaseTexture(); + const data = { + frames: {}, + meta: {}, + }; + + const spritesheet = new Spritesheet(baseTexture, data); + + expect(spritesheet.data).to.equal(data); + expect(spritesheet.baseTexture).to.equal(baseTexture); + expect(spritesheet.resolution).to.equal(1); + + spritesheet.destroy(true); + }); + + it('should create instance with scale resolution', function (done) + { + const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require + const image = new Image(); + + image.src = path.join(this.resources, data.meta.image); + image.onload = () => + { + const baseTexture = new BaseTexture(image, null, 1); + const spritesheet = new Spritesheet(baseTexture, data); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1.png'); + expect(spritesheet.resolution).to.equal(0.5); + this.validate(spritesheet, done); + }; + }); + + it('should create instance with BaseTexture source scale', function (done) + { + const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require + const baseTexture = new BaseTexture.fromImage(data.meta.image, undefined, undefined, 1.5); + const spritesheet = new Spritesheet(baseTexture, data); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1.png'); + expect(spritesheet.resolution).to.equal(0.5); + + this.validate(spritesheet, done); + }); + + it('should create instance with filename resolution', function (done) + { + const uri = path.resolve(this.resources, 'building1@2x.json'); + const data = require(uri); // eslint-disable-line global-require + const image = new Image(); + + image.src = path.join(this.resources, data.meta.image); + image.onload = () => + { + const baseTexture = new BaseTexture(image, null, 1); + const spritesheet = new Spritesheet(baseTexture, data, uri); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1@2x.png'); + expect(spritesheet.resolution).to.equal(2); + + this.validate(spritesheet, done); + }; + }); +}); diff --git a/packages/spritesheet/test/SpritesheetLoader.js b/packages/spritesheet/test/SpritesheetLoader.js new file mode 100644 index 0000000..e4205ec --- /dev/null +++ b/packages/spritesheet/test/SpritesheetLoader.js @@ -0,0 +1,214 @@ +const path = require('path'); +const { Loader, Resource } = require('@pixi/loaders'); +const { Texture, BaseTexture } = require('@pixi/core'); +const { SpritesheetLoader } = require('../'); + +describe('PIXI.SpritesheetLoader', function () +{ + it('should exist and return a function', function () + { + expect(SpritesheetLoader.middleware).to.be.a('function'); + expect(SpritesheetLoader.middleware()).to.be.a('function'); + }); + + it('should do nothing if the resource is not JSON', function () + { + const spy = sinon.spy(); + const res = {}; + + SpritesheetLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should do nothing if the resource is JSON, but improper format', function () + { + const spy = sinon.spy(); + const res = createMockResource(Resource.TYPE.JSON, {}); + + SpritesheetLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should load the image & create textures if json is properly formatted', function () + { + const spy = sinon.spy(); + const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); + const loader = new Loader(); + const addStub = sinon.stub(loader, 'add'); + const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); + + imgRes.texture = new Texture(new BaseTexture(imgRes.data)); + + addStub.yields(imgRes); + + SpritesheetLoader.middleware().call(loader, res, spy); + + addStub.restore(); + + expect(spy).to.have.been.calledOnce; + expect(addStub).to.have.been.calledWith( + `${res.name}_image`, + `${path.dirname(res.url)}/${res.data.meta.image}` + ); + expect(res).to.have.property('textures') + .that.is.an('object') + .with.keys(Object.keys(getJsonSpritesheet().frames)) + .and.has.property('0.png') + .that.is.an.instanceof(Texture); + }); + + it('should build the image url', function () + { + function getPath(url, image) + { + return SpritesheetLoader.getResourcePath({ + url, + data: { meta: { image } }, + }); + } + + let result = getPath('http://some.com/spritesheet.json', 'img.png'); + + expect(result).to.be.equals('http://some.com/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); + expect(result).to.be.equals('http://some.com/some/dir/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); + expect(result).to.be.equals('http://some.com/some/dir/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); + expect(result).to.be.equals('http://some.com/some/img.png'); + + result = getPath('/spritesheet.json', 'img.png'); + expect(result).to.be.equals('/img.png'); + + result = getPath('/some/dir/spritesheet.json', 'img.png'); + expect(result).to.be.equals('/some/dir/img.png'); + + result = getPath('/some/dir/spritesheet.json', './img.png'); + expect(result).to.be.equals('/some/dir/img.png'); + + result = getPath('/some/dir/spritesheet.json', '../img.png'); + expect(result).to.be.equals('/some/img.png'); + }); + + // TODO: Test that rectangles are created correctly. + // TODO: Test that bathc processing works correctly. + // TODO: Test that resolution processing works correctly. + // TODO: Test that metadata is honored. +}); + +function createMockResource(type, data) +{ + const name = `${Math.floor(Date.now() * Math.random())}`; + + return { + url: `http://localhost/doesnt_exist/${name}`, + name, + type, + data, + metadata: {}, + }; +} + +function getJsonSpritesheet() +{ + /* eslint-disable */ + return {"frames": { + "0.png": + { + "frame": {"x":14,"y":28,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "1.png": + { + "frame": {"x":14,"y":42,"w":12,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, + "sourceSize": {"w":12,"h":14} + }, + "2.png": + { + "frame": {"x":14,"y":14,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "3.png": + { + "frame": {"x":42,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "4.png": + { + "frame": {"x":28,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "5.png": + { + "frame": {"x":14,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "6.png": + { + "frame": {"x":0,"y":42,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "7.png": + { + "frame": {"x":0,"y":28,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "8.png": + { + "frame": {"x":0,"y":14,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "9.png": + { + "frame": {"x":0,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }}, + "meta": { + "app": "http://www.texturepacker.com", + "version": "1.0", + "image": "hud.png", + "format": "RGBA8888", + "size": {"w":64,"h":64}, + "scale": "1", + "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" + } + }; + /* eslint-enable */ +} diff --git a/packages/spritesheet/test/index.js b/packages/spritesheet/test/index.js index 35d8688..8cf5636 100644 --- a/packages/spritesheet/test/index.js +++ b/packages/spritesheet/test/index.js @@ -1,105 +1,2 @@ -const { Spritesheet } = require('../'); -const { BaseTexture, Texture } = require('@pixi/core'); -const path = require('path'); - -describe('PIXI.Spritesheet', function () -{ - before(function () - { - this.resources = path.join(__dirname, 'resources'); - this.validate = function (spritesheet, done) - { - spritesheet.parse(function (textures) - { - const id = 'goldmine_10_5.png'; - const width = Math.floor(spritesheet.data.frames[id].frame.w); - const height = Math.floor(spritesheet.data.frames[id].frame.h); - - expect(Object.keys(textures).length).to.equal(1); - expect(Object.keys(spritesheet.textures).length).to.equal(1); - expect(textures[id]).to.be.an.instanceof(Texture); - expect(textures[id].width).to.equal(width / spritesheet.resolution); - expect(textures[id].height).to.equal(height / spritesheet.resolution); - expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); - spritesheet.destroy(true); - expect(spritesheet.textures).to.be.null; - expect(spritesheet.baseTexture).to.be.null; - done(); - }); - }; - }); - - it('should exist on PIXI', function () - { - expect(Spritesheet).to.be.a.function; - expect(Spritesheet.BATCH_SIZE).to.be.a.number; - }); - - it('should create an instance', function () - { - const baseTexture = new BaseTexture(); - const data = { - frames: {}, - meta: {}, - }; - - const spritesheet = new Spritesheet(baseTexture, data); - - expect(spritesheet.data).to.equal(data); - expect(spritesheet.baseTexture).to.equal(baseTexture); - expect(spritesheet.resolution).to.equal(1); - - spritesheet.destroy(true); - }); - - it('should create instance with scale resolution', function (done) - { - const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require - const image = new Image(); - - image.src = path.join(this.resources, data.meta.image); - image.onload = () => - { - const baseTexture = new BaseTexture(image, null, 1); - const spritesheet = new Spritesheet(baseTexture, data); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1.png'); - expect(spritesheet.resolution).to.equal(0.5); - this.validate(spritesheet, done); - }; - }); - - it('should create instance with BaseTexture source scale', function (done) - { - const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require - const baseTexture = new BaseTexture.fromImage(data.meta.image, undefined, undefined, 1.5); - const spritesheet = new Spritesheet(baseTexture, data); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1.png'); - expect(spritesheet.resolution).to.equal(0.5); - - this.validate(spritesheet, done); - }); - - it('should create instance with filename resolution', function (done) - { - const uri = path.resolve(this.resources, 'building1@2x.json'); - const data = require(uri); // eslint-disable-line global-require - const image = new Image(); - - image.src = path.join(this.resources, data.meta.image); - image.onload = () => - { - const baseTexture = new BaseTexture(image, null, 1); - const spritesheet = new Spritesheet(baseTexture, data, uri); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1@2x.png'); - expect(spritesheet.resolution).to.equal(2); - - this.validate(spritesheet, done); - }; - }); -}); +require('./Spritesheet'); +require('./SpritesheetLoader'); diff --git a/packages/text-bitmap/package.json b/packages/text-bitmap/package.json index a8a5f95..4b47da6 100644 --- a/packages/text-bitmap/package.json +++ b/packages/text-bitmap/package.json @@ -23,7 +23,7 @@ "build": "rollup -cp", "watch": "rollup -cw", "postversion": "npm run build", - "test": "exit 0" + "test": "tester" }, "files": [ "lib" @@ -31,12 +31,15 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha", "@pixi/sprite": "^5.0.0-alpha", "@pixi/settings": "^5.0.0-alpha", "@pixi/math": "^5.0.0-alpha", "@pixi/display": "^5.0.0-alpha" }, "devDependencies": { + "@pixi/spritesheet": "^5.0.0-alpha", + "@internal/tester": "^5.0.0-alpha", "@internal/builder": "^5.0.0-alpha", "rollup": "^0.50.0" } diff --git a/packages/text-bitmap/src/BitmapFontLoader.js b/packages/text-bitmap/src/BitmapFontLoader.js new file mode 100644 index 0000000..3956060 --- /dev/null +++ b/packages/text-bitmap/src/BitmapFontLoader.js @@ -0,0 +1,112 @@ +import * as path from 'path'; +import { TextureCache } from '@pixi/utils'; +import { Resource, Loader } from '@pixi/loaders'; +import BitmapText from './BitmapText'; + +/** + * {@link PIXI.loaders.Loader Loader} middleware for loading + * bitmap-based fonts suitable for using with {@link PIXI.BitmapText}. + * @class + * @memberof PIXI + */ +export default class BitmapFontLoader +{ + /** + * Register a BitmapText font from loader resource. + * + * @param {PIXI.loaders.Resource} resource - Loader resource. + * @param {PIXI.Texture} texture - Reference to texture. + */ + static parse(resource, texture) + { + resource.bitmapFont = BitmapText.registerFont(resource.data, texture); + } + + /** + * Middleware function to support BitmapText fonts, this is automatically installed. + * @see PIXI.loaders.Loader.useMiddleware + */ + static middleware() + { + return function bitmapFontLoader(resource, next) + { + // skip if no data or not xml data + if (!resource.data || resource.type !== Resource.TYPE.XML) + { + next(); + + return; + } + + // skip if not bitmap font data, using some silly duck-typing + if (resource.data.getElementsByTagName('page').length === 0 + || resource.data.getElementsByTagName('info').length === 0 + || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null + ) + { + next(); + + return; + } + + let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; + + if (resource.isDataUrl) + { + if (xmlUrl === '.') + { + xmlUrl = ''; + } + + if (this.baseUrl && xmlUrl) + { + // if baseurl has a trailing slash then add one to xmlUrl so the replace works below + if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') + { + xmlUrl += '/'; + } + } + } + + // remove baseUrl from xmlUrl + xmlUrl = xmlUrl.replace(this.baseUrl, ''); + + // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. + if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') + { + xmlUrl += '/'; + } + + const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); + + if (TextureCache[textureUrl]) + { + // reuse existing texture + BitmapFontLoader.parse(resource, TextureCache[textureUrl]); + next(); + } + else + { + const loadOptions = { + crossOrigin: resource.crossOrigin, + loadType: Resource.LOAD_TYPE.IMAGE, + metadata: resource.metadata.imageMetadata, + parentResource: resource, + }; + + // load the texture for the font + this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => + { + BitmapFontLoader.parse(resource, res.texture); + next(); + }); + } + }; + } +} + +// Add custom add support for loading fnt files as XML +Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); + +// Install loader support for BitmapText +Loader.useMiddleware(BitmapFontLoader.middleware); diff --git a/packages/text-bitmap/src/index.js b/packages/text-bitmap/src/index.js index a98e7b4..e5afd2c 100644 --- a/packages/text-bitmap/src/index.js +++ b/packages/text-bitmap/src/index.js @@ -1 +1,2 @@ export { default as BitmapText } from './BitmapText'; +export { default as BitmapFontLoader } from './BitmapFontLoader'; diff --git a/packages/text-bitmap/test/.eslintrc.json b/packages/text-bitmap/test/.eslintrc.json new file mode 100644 index 0000000..2094b04 --- /dev/null +++ b/packages/text-bitmap/test/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "globals": { + "expect": false, + "assert": false, + "sinon": false, + "PIXI": false + }, + "rules": { + "func-names": 0, + "no-unused-expressions": 0 + } +} \ No newline at end of file diff --git a/packages/text-bitmap/test/BitmapFontLoader.js b/packages/text-bitmap/test/BitmapFontLoader.js new file mode 100644 index 0000000..a76fc1c --- /dev/null +++ b/packages/text-bitmap/test/BitmapFontLoader.js @@ -0,0 +1,319 @@ +const path = require('path'); +const fs = require('fs'); +const { BaseTextureCache, TextureCache } = require('@pixi/utils'); +const { Texture, BaseTexture } = require('@pixi/core'); +const { Spritesheet } = require('@pixi/spritesheet'); +const { BitmapText, BitmapFontLoader } = require('../'); + +describe('PIXI.BitmapFontLoader', function () +{ + afterEach(function () + { + for (const font in BitmapText.fonts) + { + delete BitmapText.fonts[font]; + } + for (const baseTexture in BaseTextureCache) + { + delete BaseTextureCache[baseTexture]; + } + for (const texture in TextureCache) + { + delete TextureCache[texture]; + } + }); + + before(function (done) + { + const resolveURL = (url) => path.resolve(this.resources, url); + + this.resources = path.join(__dirname, 'resources'); + this.fontXML = null; + this.fontScaledXML = null; + this.fontImage = null; + this.fontScaledImage = null; + this.atlasImage = null; + this.atlasScaledImage = null; + this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require + this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require + + const loadXML = (url) => new Promise((resolve) => + fs.readFile(resolveURL(url), 'utf8', (err, data) => + { + expect(err).to.be.null; + resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); + })); + + const loadImage = (url) => new Promise((resolve) => + { + const image = new Image(); + + image.onload = () => resolve(image); + image.src = resolveURL(url); + }); + + Promise.all([ + loadXML('font.fnt'), + loadXML('font@0.5x.fnt'), + loadImage('font.png'), + loadImage('font@0.5x.png'), + loadImage('atlas.png'), + loadImage('atlas@0.5x.png'), + ]).then(([ + fontXML, + fontScaledXML, + fontImage, + fontScaledImage, + atlasImage, + atlasScaledImage, + ]) => + { + this.fontXML = fontXML; + this.fontScaledXML = fontScaledXML; + this.fontImage = fontImage; + this.fontScaledImage = fontScaledImage; + this.atlasImage = atlasImage; + this.atlasScaledImage = atlasScaledImage; + done(); + }); + }); + + it('should exist and return a function', function () + { + expect(BitmapFontLoader.middleware).to.be.a('function'); + expect(BitmapFontLoader.middleware()).to.be.a('function'); + }); + + it('should do nothing if the resource is not XML', function () + { + const spy = sinon.spy(); + const res = {}; + + BitmapFontLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should do nothing if the resource is not properly formatted XML', function () + { + const spy = sinon.spy(); + const res = { data: document.createDocumentFragment() }; + + BitmapFontLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + // TODO: Test the texture cache code path. + // TODO: Test the loading texture code path. + // TODO: Test data-url code paths. + + it('should properly register bitmap font', function (done) + { + const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); + 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charA.texture.frame.x).to.equal(2); + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charB.texture.frame.x).to.equal(2); + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charC.texture.frame.x).to.equal(23); + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charD.texture.frame.x).to.equal(19); + 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]; + + expect(charE).to.be.undefined; + done(); + }); + + it('should properly register SCALED bitmap font', function (done) + { + const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); + const font = BitmapText.registerFont(this.fontScaledXML, 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 + 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]; + + expect(charE).to.be.undefined; + done(); + }); + + it('should properly register bitmap font NESTED into spritesheet', function (done) + { + const baseTexture = new BaseTexture(this.atlasImage, null, 1); + const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); + + spritesheet.parse(() => + { + const fontTexture = Texture.fromFrame('resources/font.png'); + const font = BitmapText.registerFont(this.fontXML, fontTexture); + const fontX = 158; // bare value from spritesheet frame + const fontY = 2; // bare value from spritesheet frame + + 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charA.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charB.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charC.texture.frame.x).to.equal(fontX + 23); + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charD.texture.frame.x).to.equal(fontX + 19); + 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]; + + expect(charE).to.be.undefined; + done(); + }); + }); + + it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) + { + const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); + const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); + + spritesheet.resolution = 1; + + spritesheet.parse(() => + { + const fontTexture = Texture.fromFrame('resources/font.png'); + const font = BitmapText.registerFont(this.fontXML, fontTexture); + const fontX = 158; // bare value from spritesheet frame + const fontY = 2; // bare value from spritesheet frame + + 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charA.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charB.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charC.texture.frame.x).to.equal(fontX + 23); + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charD.texture.frame.x).to.equal(fontX + 19); + 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]; + + expect(charE).to.be.undefined; + done(); + }); + }); + + it('should parse exist', function () + { + expect(BitmapFontLoader.parse).to.be.a('function'); + }); +}); diff --git a/packages/text-bitmap/test/index.js b/packages/text-bitmap/test/index.js new file mode 100644 index 0000000..688fee2 --- /dev/null +++ b/packages/text-bitmap/test/index.js @@ -0,0 +1 @@ +require('./BitmapFontLoader'); diff --git a/packages/text-bitmap/test/resources/atlas.json b/packages/text-bitmap/test/resources/atlas.json new file mode 100644 index 0000000..86e65a5 --- /dev/null +++ b/packages/text-bitmap/test/resources/atlas.json @@ -0,0 +1,25 @@ +{ + "meta": { + "image": "atlas.png", + "size": {"w":256,"h":256}, + "scale": "1" + }, + "frames": { + "resources/test.png": + { + "frame": {"x":2,"y":2,"w":152,"h":188}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, + "sourceSize": {"w":300,"h":225} + }, + "resources/font.png": + { + "frame": {"x":158,"y":2,"w":40,"h":42}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, + "sourceSize": {"w":43,"h":46} + } + } +} \ No newline at end of file diff --git a/packages/text-bitmap/test/resources/atlas.png b/packages/text-bitmap/test/resources/atlas.png new file mode 100644 index 0000000..d5e7892 --- /dev/null +++ b/packages/text-bitmap/test/resources/atlas.png Binary files differ diff --git a/packages/text-bitmap/test/resources/atlas@0.5x.json b/packages/text-bitmap/test/resources/atlas@0.5x.json new file mode 100644 index 0000000..ae990a1 --- /dev/null +++ b/packages/text-bitmap/test/resources/atlas@0.5x.json @@ -0,0 +1,25 @@ +{ + "meta": { + "image": "atlas@0.5x.png", + "size": {"w":256,"h":256}, + "scale": "1" + }, + "frames": { + "resources/test.png": + { + "frame": {"x":2,"y":2,"w":152,"h":188}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, + "sourceSize": {"w":300,"h":225} + }, + "resources/font.png": + { + "frame": {"x":158,"y":2,"w":40,"h":42}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, + "sourceSize": {"w":43,"h":46} + } + } +} \ No newline at end of file diff --git a/packages/text-bitmap/test/resources/atlas@0.5x.png b/packages/text-bitmap/test/resources/atlas@0.5x.png new file mode 100644 index 0000000..d5e7892 --- /dev/null +++ b/packages/text-bitmap/test/resources/atlas@0.5x.png Binary files differ diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/packages/loaders/src/spritesheetParser.js b/packages/loaders/src/spritesheetParser.js deleted file mode 100644 index f7fa206..0000000 --- a/packages/loaders/src/spritesheetParser.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Resource } from 'resource-loader'; -import url from 'url'; -import { Spritesheet } from '@pixi/spritesheet'; - -export default function () -{ - return function spritesheetParser(resource, next) - { - const imageResourceName = `${resource.name}_image`; - - // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists - if (!resource.data - || resource.type !== Resource.TYPE.JSON - || !resource.data.frames - || this.resources[imageResourceName] - ) - { - next(); - - return; - } - - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - const resourcePath = getResourcePath(resource, this.baseUrl); - - // load the image for this sheet - this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) - { - const spritesheet = new Spritesheet( - res.texture.baseTexture, - resource.data, - resource.url - ); - - spritesheet.parse(() => - { - resource.spritesheet = spritesheet; - resource.textures = spritesheet.textures; - next(); - }); - }); - }; -} - -export function getResourcePath(resource, baseUrl) -{ - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - return resource.data.meta.image; - } - - return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); -} diff --git a/packages/loaders/test/bitmapFontParser.js b/packages/loaders/test/bitmapFontParser.js deleted file mode 100644 index 43ef194..0000000 --- a/packages/loaders/test/bitmapFontParser.js +++ /dev/null @@ -1,325 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const { BitmapText } = require('@pixi/text-bitmap'); -const { BaseTextureCache, TextureCache } = require('@pixi/utils'); -const { Texture, BaseTexture } = require('@pixi/core'); -const { Spritesheet } = require('@pixi/spritesheet'); -const { bitmapFontParser, parseBitmapFontData } = require('../'); - -describe('PIXI.loaders.bitmapFontParser', function () -{ - afterEach(function () - { - for (const font in BitmapText.fonts) - { - delete BitmapText.fonts[font]; - } - for (const baseTexture in BaseTextureCache) - { - delete BaseTextureCache[baseTexture]; - } - for (const texture in TextureCache) - { - delete TextureCache[texture]; - } - }); - - before(function (done) - { - const resolveURL = (url) => path.resolve(this.resources, url); - - this.resources = path.join(__dirname, 'resources'); - this.fontXML = null; - this.fontScaledXML = null; - this.fontImage = null; - this.fontScaledImage = null; - this.atlasImage = null; - this.atlasScaledImage = null; - this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require - this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require - - const loadXML = (url) => new Promise((resolve) => - fs.readFile(resolveURL(url), 'utf8', (err, data) => - { - expect(err).to.be.null; - resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); - })); - - const loadImage = (url) => new Promise((resolve) => - { - const image = new Image(); - - image.onload = () => resolve(image); - image.src = resolveURL(url); - }); - - Promise.all([ - loadXML('font.fnt'), - loadXML('font@0.5x.fnt'), - loadImage('font.png'), - loadImage('font@0.5x.png'), - loadImage('atlas.png'), - loadImage('atlas@0.5x.png'), - ]).then(([ - fontXML, - fontScaledXML, - fontImage, - fontScaledImage, - atlasImage, - atlasScaledImage, - ]) => - { - this.fontXML = fontXML; - this.fontScaledXML = fontScaledXML; - this.fontImage = fontImage; - this.fontScaledImage = fontScaledImage; - this.atlasImage = atlasImage; - this.atlasScaledImage = atlasScaledImage; - done(); - }); - }); - - it('should exist and return a function', function () - { - expect(bitmapFontParser).to.be.a('function'); - expect(bitmapFontParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not XML', function () - { - const spy = sinon.spy(); - const res = {}; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is not properly formatted XML', function () - { - const spy = sinon.spy(); - const res = { data: document.createDocumentFragment() }; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - // TODO: Test the texture cache code path. - // TODO: Test the loading texture code path. - // TODO: Test data-url code paths. - - it('should properly register bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charA.texture.frame.x).to.equal(2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charB.texture.frame.x).to.equal(2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charC.texture.frame.x).to.equal(23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charD.texture.frame.x).to.equal(19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register SCALED bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); - const font = BitmapText.registerFont(this.fontScaledXML, 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register bitmap font NESTED into spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); - - it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); - - spritesheet.resolution = 1; - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); -}); - -describe('PIXI.loaders.parseBitmapFontData', function () -{ - it('should exist', function () - { - expect(parseBitmapFontData).to.be.a('function'); - }); - - // TODO: Test the parser code. -}); diff --git a/packages/loaders/test/index.js b/packages/loaders/test/index.js index f00de5f..72c6554 100644 --- a/packages/loaders/test/index.js +++ b/packages/loaders/test/index.js @@ -1,4 +1,2 @@ -require('./bitmapFontParser'); require('./loader'); -require('./spritesheetParser'); require('./textureParser'); diff --git a/packages/loaders/test/resources/atlas.json b/packages/loaders/test/resources/atlas.json deleted file mode 100644 index 86e65a5..0000000 --- a/packages/loaders/test/resources/atlas.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas.png b/packages/loaders/test/resources/atlas.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/atlas@0.5x.json b/packages/loaders/test/resources/atlas@0.5x.json deleted file mode 100644 index ae990a1..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas@0.5x.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas@0.5x.png b/packages/loaders/test/resources/atlas@0.5x.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font.fnt b/packages/loaders/test/resources/font.fnt deleted file mode 100644 index 56e1060..0000000 --- a/packages/loaders/test/resources/font.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font.png b/packages/loaders/test/resources/font.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font@0.5x.fnt b/packages/loaders/test/resources/font@0.5x.fnt deleted file mode 100644 index 6c247c7..0000000 --- a/packages/loaders/test/resources/font@0.5x.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font@0.5x.png b/packages/loaders/test/resources/font@0.5x.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/spritesheetParser.js b/packages/loaders/test/spritesheetParser.js deleted file mode 100644 index 98890b8..0000000 --- a/packages/loaders/test/spritesheetParser.js +++ /dev/null @@ -1,213 +0,0 @@ -const path = require('path'); -const { spritesheetParser, Resource, Loader, getResourcePath } = require('../'); -const { Texture, BaseTexture } = require('@pixi/core'); - -describe('PIXI.loaders.spritesheetParser', function () -{ - it('should exist and return a function', function () - { - expect(spritesheetParser).to.be.a('function'); - expect(spritesheetParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not JSON', function () - { - const spy = sinon.spy(); - const res = {}; - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is JSON, but improper format', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, {}); - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should load the image & create textures if json is properly formatted', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); - const loader = new Loader(); - const addStub = sinon.stub(loader, 'add'); - const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); - - imgRes.texture = new Texture(new BaseTexture(imgRes.data)); - - addStub.yields(imgRes); - - spritesheetParser().call(loader, res, spy); - - addStub.restore(); - - expect(spy).to.have.been.calledOnce; - expect(addStub).to.have.been.calledWith( - `${res.name}_image`, - `${path.dirname(res.url)}/${res.data.meta.image}` - ); - expect(res).to.have.property('textures') - .that.is.an('object') - .with.keys(Object.keys(getJsonSpritesheet().frames)) - .and.has.property('0.png') - .that.is.an.instanceof(Texture); - }); - - it('should build the image url', function () - { - function getPath(url, image) - { - return getResourcePath({ - url, - data: { meta: { image } }, - }); - } - - let result = getPath('http://some.com/spritesheet.json', 'img.png'); - - expect(result).to.be.equals('http://some.com/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('http://some.com/some/img.png'); - - result = getPath('/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/img.png'); - - result = getPath('/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('/some/img.png'); - }); - - // TODO: Test that rectangles are created correctly. - // TODO: Test that bathc processing works correctly. - // TODO: Test that resolution processing works correctly. - // TODO: Test that metadata is honored. -}); - -function createMockResource(type, data) -{ - const name = `${Math.floor(Date.now() * Math.random())}`; - - return { - url: `http://localhost/doesnt_exist/${name}`, - name, - type, - data, - metadata: {}, - }; -} - -function getJsonSpritesheet() -{ - /* eslint-disable */ - return {"frames": { - "0.png": - { - "frame": {"x":14,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "1.png": - { - "frame": {"x":14,"y":42,"w":12,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, - "sourceSize": {"w":12,"h":14} - }, - "2.png": - { - "frame": {"x":14,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "3.png": - { - "frame": {"x":42,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "4.png": - { - "frame": {"x":28,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "5.png": - { - "frame": {"x":14,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "6.png": - { - "frame": {"x":0,"y":42,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "7.png": - { - "frame": {"x":0,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "8.png": - { - "frame": {"x":0,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "9.png": - { - "frame": {"x":0,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }}, - "meta": { - "app": "http://www.texturepacker.com", - "version": "1.0", - "image": "hud.png", - "format": "RGBA8888", - "size": {"w":64,"h":64}, - "scale": "1", - "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" - } - }; - /* eslint-enable */ -} diff --git a/packages/mixin-app-loader/LICENSE b/packages/mixin-app-loader/LICENSE new file mode 100644 index 0000000..5d16b50 --- /dev/null +++ b/packages/mixin-app-loader/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2013-2017 Mathew Groves, Chad Engler + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/packages/mixin-app-loader/README.md b/packages/mixin-app-loader/README.md new file mode 100644 index 0000000..3e89f86 --- /dev/null +++ b/packages/mixin-app-loader/README.md @@ -0,0 +1,13 @@ +# @pixi/mixin-app-loader + +## Installation + +```bash +npm install @pixi/mixin-app-loader +``` + +## Usage + +```js +import '@pixi/mixin-app-loader'; +``` \ No newline at end of file diff --git a/packages/mixin-app-loader/package.json b/packages/mixin-app-loader/package.json new file mode 100644 index 0000000..869ed4e --- /dev/null +++ b/packages/mixin-app-loader/package.json @@ -0,0 +1,41 @@ +{ + "name": "@pixi/mixin-app-loader", + "version": "5.0.0-alpha", + "main": "lib/mixin-app-loader.js", + "module": "lib/mixin-app-loader.es.js", + "description": "Support for loader in Application", + "author": "Mat Groves", + "contributors": [ + "Matt Karl " + ], + "homepage": "http://pixijs.com/", + "bugs": "https://github.com/pixijs/pixi.js/issues", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/pixijs/pixi.js.git" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "build:dev": "rollup -c", + "build": "rollup -cp", + "watch": "rollup -cw", + "postversion": "npm run build", + "test": "tester" + }, + "files": [ + "lib" + ], + "dependencies": { + "@pixi/app": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" + }, + "devDependencies": { + "@pixi/utils": "^5.0.0-alpha", + "@internal/tester": "^5.0.0-alpha", + "@internal/builder": "^5.0.0-alpha", + "rollup": "^0.50.0" + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/rollup.config.js b/packages/mixin-app-loader/rollup.config.js new file mode 100644 index 0000000..a73c874 --- /dev/null +++ b/packages/mixin-app-loader/rollup.config.js @@ -0,0 +1 @@ +export { default } from '@internal/builder'; diff --git a/packages/mixin-app-loader/src/index.js b/packages/mixin-app-loader/src/index.js new file mode 100644 index 0000000..354997a --- /dev/null +++ b/packages/mixin-app-loader/src/index.js @@ -0,0 +1,38 @@ +import { Application } from '@pixi/app'; +import { Loader, shared } from '@pixi/loaders'; + +Application.prototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperties(Application.prototype, { + loader: { + get() + { + if (!this._loader && this._options) + { + const { sharedLoader } = this._options; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +Application.prototype._parentDestroy = Application.prototype.destroy; +Application.prototype.destroy = function destroy(removeView) +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(removeView); +}; diff --git a/packages/mixin-app-loader/test/.eslintrc.json b/packages/mixin-app-loader/test/.eslintrc.json new file mode 100644 index 0000000..2094b04 --- /dev/null +++ b/packages/mixin-app-loader/test/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "globals": { + "expect": false, + "assert": false, + "sinon": false, + "PIXI": false + }, + "rules": { + "func-names": 0, + "no-unused-expressions": 0 + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/test/index.js b/packages/mixin-app-loader/test/index.js new file mode 100644 index 0000000..3468fe5 --- /dev/null +++ b/packages/mixin-app-loader/test/index.js @@ -0,0 +1,35 @@ +const { Application } = require('@pixi/app'); +const { Loader, shared } = require('@pixi/loaders'); +const { skipHello } = require('@pixi/utils'); + +require('../'); + +skipHello(); + +describe('PIXI.Application#loader', function () +{ + it('should contain loader property', function () + { + const obj = new Application(); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); + + it('should use sharedLoader option', function () + { + const obj = new Application({ sharedLoader: true }); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + expect(obj.loader).to.equal(shared); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); +}); diff --git a/packages/spritesheet/package.json b/packages/spritesheet/package.json index 34749e2..5f35442 100644 --- a/packages/spritesheet/package.json +++ b/packages/spritesheet/package.json @@ -31,7 +31,8 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/math": "^5.0.0-alpha", - "@pixi/utils": "^5.0.0-alpha" + "@pixi/utils": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" }, "devDependencies": { "@internal/builder": "^5.0.0-alpha", diff --git a/packages/spritesheet/src/SpritesheetLoader.js b/packages/spritesheet/src/SpritesheetLoader.js new file mode 100644 index 0000000..c7b34ea --- /dev/null +++ b/packages/spritesheet/src/SpritesheetLoader.js @@ -0,0 +1,83 @@ +import url from 'url'; +import { Resource, Loader } from '@pixi/loaders'; +import Spritesheet from './Spritesheet'; + +/** + * {@link PIXI.loaders.Loader Loader} middleware for loading + * texture atlases that have been created with TexturePacker or + * similar JSON-based spritesheet. This automatically generates + * Texture resources. + * @class + * @memberof PIXI + */ +export default class SpritesheetLoader +{ + /** + * Middleware function to support Spritesheets, this is automatically installed. + * @see PIXI.loaders.Loader.useMiddleware + */ + static middleware() + { + return function spritesheetLoader(resource, next) + { + const imageResourceName = `${resource.name}_image`; + + // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists + if (!resource.data + || resource.type !== Resource.TYPE.JSON + || !resource.data.frames + || this.resources[imageResourceName] + ) + { + next(); + + return; + } + + const loadOptions = { + crossOrigin: resource.crossOrigin, + loadType: Resource.LOAD_TYPE.IMAGE, + metadata: resource.metadata.imageMetadata, + parentResource: resource, + }; + + const resourcePath = SpritesheetLoader.getResourcePath(resource, this.baseUrl); + + // load the image for this sheet + this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) + { + const spritesheet = new Spritesheet( + res.texture.baseTexture, + resource.data, + resource.url + ); + + spritesheet.parse(() => + { + resource.spritesheet = spritesheet; + resource.textures = spritesheet.textures; + next(); + }); + }); + }; + } + + /** + * Get the spritesheets root path + * @param {PIXI.loader.Resource} resource - Resource to check path + * @param {string} baseUrl - Base root url + */ + static getResourcePath(resource, baseUrl) + { + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); + } +} + +// Install loader support for Spritesheet objects +Loader.useMiddleware(SpritesheetLoader.middleware); diff --git a/packages/spritesheet/src/index.js b/packages/spritesheet/src/index.js index 51d96a1..b9ee1e8 100644 --- a/packages/spritesheet/src/index.js +++ b/packages/spritesheet/src/index.js @@ -1 +1,2 @@ export { default as Spritesheet } from './Spritesheet'; +export { default as SpritesheetLoader } from './SpritesheetLoader'; diff --git a/packages/spritesheet/test/Spritesheet.js b/packages/spritesheet/test/Spritesheet.js new file mode 100644 index 0000000..35d8688 --- /dev/null +++ b/packages/spritesheet/test/Spritesheet.js @@ -0,0 +1,105 @@ +const { Spritesheet } = require('../'); +const { BaseTexture, Texture } = require('@pixi/core'); +const path = require('path'); + +describe('PIXI.Spritesheet', function () +{ + before(function () + { + this.resources = path.join(__dirname, 'resources'); + this.validate = function (spritesheet, done) + { + spritesheet.parse(function (textures) + { + const id = 'goldmine_10_5.png'; + const width = Math.floor(spritesheet.data.frames[id].frame.w); + const height = Math.floor(spritesheet.data.frames[id].frame.h); + + expect(Object.keys(textures).length).to.equal(1); + expect(Object.keys(spritesheet.textures).length).to.equal(1); + expect(textures[id]).to.be.an.instanceof(Texture); + expect(textures[id].width).to.equal(width / spritesheet.resolution); + expect(textures[id].height).to.equal(height / spritesheet.resolution); + expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); + spritesheet.destroy(true); + expect(spritesheet.textures).to.be.null; + expect(spritesheet.baseTexture).to.be.null; + done(); + }); + }; + }); + + it('should exist on PIXI', function () + { + expect(Spritesheet).to.be.a.function; + expect(Spritesheet.BATCH_SIZE).to.be.a.number; + }); + + it('should create an instance', function () + { + const baseTexture = new BaseTexture(); + const data = { + frames: {}, + meta: {}, + }; + + const spritesheet = new Spritesheet(baseTexture, data); + + expect(spritesheet.data).to.equal(data); + expect(spritesheet.baseTexture).to.equal(baseTexture); + expect(spritesheet.resolution).to.equal(1); + + spritesheet.destroy(true); + }); + + it('should create instance with scale resolution', function (done) + { + const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require + const image = new Image(); + + image.src = path.join(this.resources, data.meta.image); + image.onload = () => + { + const baseTexture = new BaseTexture(image, null, 1); + const spritesheet = new Spritesheet(baseTexture, data); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1.png'); + expect(spritesheet.resolution).to.equal(0.5); + this.validate(spritesheet, done); + }; + }); + + it('should create instance with BaseTexture source scale', function (done) + { + const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require + const baseTexture = new BaseTexture.fromImage(data.meta.image, undefined, undefined, 1.5); + const spritesheet = new Spritesheet(baseTexture, data); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1.png'); + expect(spritesheet.resolution).to.equal(0.5); + + this.validate(spritesheet, done); + }); + + it('should create instance with filename resolution', function (done) + { + const uri = path.resolve(this.resources, 'building1@2x.json'); + const data = require(uri); // eslint-disable-line global-require + const image = new Image(); + + image.src = path.join(this.resources, data.meta.image); + image.onload = () => + { + const baseTexture = new BaseTexture(image, null, 1); + const spritesheet = new Spritesheet(baseTexture, data, uri); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1@2x.png'); + expect(spritesheet.resolution).to.equal(2); + + this.validate(spritesheet, done); + }; + }); +}); diff --git a/packages/spritesheet/test/SpritesheetLoader.js b/packages/spritesheet/test/SpritesheetLoader.js new file mode 100644 index 0000000..e4205ec --- /dev/null +++ b/packages/spritesheet/test/SpritesheetLoader.js @@ -0,0 +1,214 @@ +const path = require('path'); +const { Loader, Resource } = require('@pixi/loaders'); +const { Texture, BaseTexture } = require('@pixi/core'); +const { SpritesheetLoader } = require('../'); + +describe('PIXI.SpritesheetLoader', function () +{ + it('should exist and return a function', function () + { + expect(SpritesheetLoader.middleware).to.be.a('function'); + expect(SpritesheetLoader.middleware()).to.be.a('function'); + }); + + it('should do nothing if the resource is not JSON', function () + { + const spy = sinon.spy(); + const res = {}; + + SpritesheetLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should do nothing if the resource is JSON, but improper format', function () + { + const spy = sinon.spy(); + const res = createMockResource(Resource.TYPE.JSON, {}); + + SpritesheetLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should load the image & create textures if json is properly formatted', function () + { + const spy = sinon.spy(); + const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); + const loader = new Loader(); + const addStub = sinon.stub(loader, 'add'); + const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); + + imgRes.texture = new Texture(new BaseTexture(imgRes.data)); + + addStub.yields(imgRes); + + SpritesheetLoader.middleware().call(loader, res, spy); + + addStub.restore(); + + expect(spy).to.have.been.calledOnce; + expect(addStub).to.have.been.calledWith( + `${res.name}_image`, + `${path.dirname(res.url)}/${res.data.meta.image}` + ); + expect(res).to.have.property('textures') + .that.is.an('object') + .with.keys(Object.keys(getJsonSpritesheet().frames)) + .and.has.property('0.png') + .that.is.an.instanceof(Texture); + }); + + it('should build the image url', function () + { + function getPath(url, image) + { + return SpritesheetLoader.getResourcePath({ + url, + data: { meta: { image } }, + }); + } + + let result = getPath('http://some.com/spritesheet.json', 'img.png'); + + expect(result).to.be.equals('http://some.com/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); + expect(result).to.be.equals('http://some.com/some/dir/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); + expect(result).to.be.equals('http://some.com/some/dir/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); + expect(result).to.be.equals('http://some.com/some/img.png'); + + result = getPath('/spritesheet.json', 'img.png'); + expect(result).to.be.equals('/img.png'); + + result = getPath('/some/dir/spritesheet.json', 'img.png'); + expect(result).to.be.equals('/some/dir/img.png'); + + result = getPath('/some/dir/spritesheet.json', './img.png'); + expect(result).to.be.equals('/some/dir/img.png'); + + result = getPath('/some/dir/spritesheet.json', '../img.png'); + expect(result).to.be.equals('/some/img.png'); + }); + + // TODO: Test that rectangles are created correctly. + // TODO: Test that bathc processing works correctly. + // TODO: Test that resolution processing works correctly. + // TODO: Test that metadata is honored. +}); + +function createMockResource(type, data) +{ + const name = `${Math.floor(Date.now() * Math.random())}`; + + return { + url: `http://localhost/doesnt_exist/${name}`, + name, + type, + data, + metadata: {}, + }; +} + +function getJsonSpritesheet() +{ + /* eslint-disable */ + return {"frames": { + "0.png": + { + "frame": {"x":14,"y":28,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "1.png": + { + "frame": {"x":14,"y":42,"w":12,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, + "sourceSize": {"w":12,"h":14} + }, + "2.png": + { + "frame": {"x":14,"y":14,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "3.png": + { + "frame": {"x":42,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "4.png": + { + "frame": {"x":28,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "5.png": + { + "frame": {"x":14,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "6.png": + { + "frame": {"x":0,"y":42,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "7.png": + { + "frame": {"x":0,"y":28,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "8.png": + { + "frame": {"x":0,"y":14,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "9.png": + { + "frame": {"x":0,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }}, + "meta": { + "app": "http://www.texturepacker.com", + "version": "1.0", + "image": "hud.png", + "format": "RGBA8888", + "size": {"w":64,"h":64}, + "scale": "1", + "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" + } + }; + /* eslint-enable */ +} diff --git a/packages/spritesheet/test/index.js b/packages/spritesheet/test/index.js index 35d8688..8cf5636 100644 --- a/packages/spritesheet/test/index.js +++ b/packages/spritesheet/test/index.js @@ -1,105 +1,2 @@ -const { Spritesheet } = require('../'); -const { BaseTexture, Texture } = require('@pixi/core'); -const path = require('path'); - -describe('PIXI.Spritesheet', function () -{ - before(function () - { - this.resources = path.join(__dirname, 'resources'); - this.validate = function (spritesheet, done) - { - spritesheet.parse(function (textures) - { - const id = 'goldmine_10_5.png'; - const width = Math.floor(spritesheet.data.frames[id].frame.w); - const height = Math.floor(spritesheet.data.frames[id].frame.h); - - expect(Object.keys(textures).length).to.equal(1); - expect(Object.keys(spritesheet.textures).length).to.equal(1); - expect(textures[id]).to.be.an.instanceof(Texture); - expect(textures[id].width).to.equal(width / spritesheet.resolution); - expect(textures[id].height).to.equal(height / spritesheet.resolution); - expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); - spritesheet.destroy(true); - expect(spritesheet.textures).to.be.null; - expect(spritesheet.baseTexture).to.be.null; - done(); - }); - }; - }); - - it('should exist on PIXI', function () - { - expect(Spritesheet).to.be.a.function; - expect(Spritesheet.BATCH_SIZE).to.be.a.number; - }); - - it('should create an instance', function () - { - const baseTexture = new BaseTexture(); - const data = { - frames: {}, - meta: {}, - }; - - const spritesheet = new Spritesheet(baseTexture, data); - - expect(spritesheet.data).to.equal(data); - expect(spritesheet.baseTexture).to.equal(baseTexture); - expect(spritesheet.resolution).to.equal(1); - - spritesheet.destroy(true); - }); - - it('should create instance with scale resolution', function (done) - { - const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require - const image = new Image(); - - image.src = path.join(this.resources, data.meta.image); - image.onload = () => - { - const baseTexture = new BaseTexture(image, null, 1); - const spritesheet = new Spritesheet(baseTexture, data); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1.png'); - expect(spritesheet.resolution).to.equal(0.5); - this.validate(spritesheet, done); - }; - }); - - it('should create instance with BaseTexture source scale', function (done) - { - const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require - const baseTexture = new BaseTexture.fromImage(data.meta.image, undefined, undefined, 1.5); - const spritesheet = new Spritesheet(baseTexture, data); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1.png'); - expect(spritesheet.resolution).to.equal(0.5); - - this.validate(spritesheet, done); - }); - - it('should create instance with filename resolution', function (done) - { - const uri = path.resolve(this.resources, 'building1@2x.json'); - const data = require(uri); // eslint-disable-line global-require - const image = new Image(); - - image.src = path.join(this.resources, data.meta.image); - image.onload = () => - { - const baseTexture = new BaseTexture(image, null, 1); - const spritesheet = new Spritesheet(baseTexture, data, uri); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1@2x.png'); - expect(spritesheet.resolution).to.equal(2); - - this.validate(spritesheet, done); - }; - }); -}); +require('./Spritesheet'); +require('./SpritesheetLoader'); diff --git a/packages/text-bitmap/package.json b/packages/text-bitmap/package.json index a8a5f95..4b47da6 100644 --- a/packages/text-bitmap/package.json +++ b/packages/text-bitmap/package.json @@ -23,7 +23,7 @@ "build": "rollup -cp", "watch": "rollup -cw", "postversion": "npm run build", - "test": "exit 0" + "test": "tester" }, "files": [ "lib" @@ -31,12 +31,15 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha", "@pixi/sprite": "^5.0.0-alpha", "@pixi/settings": "^5.0.0-alpha", "@pixi/math": "^5.0.0-alpha", "@pixi/display": "^5.0.0-alpha" }, "devDependencies": { + "@pixi/spritesheet": "^5.0.0-alpha", + "@internal/tester": "^5.0.0-alpha", "@internal/builder": "^5.0.0-alpha", "rollup": "^0.50.0" } diff --git a/packages/text-bitmap/src/BitmapFontLoader.js b/packages/text-bitmap/src/BitmapFontLoader.js new file mode 100644 index 0000000..3956060 --- /dev/null +++ b/packages/text-bitmap/src/BitmapFontLoader.js @@ -0,0 +1,112 @@ +import * as path from 'path'; +import { TextureCache } from '@pixi/utils'; +import { Resource, Loader } from '@pixi/loaders'; +import BitmapText from './BitmapText'; + +/** + * {@link PIXI.loaders.Loader Loader} middleware for loading + * bitmap-based fonts suitable for using with {@link PIXI.BitmapText}. + * @class + * @memberof PIXI + */ +export default class BitmapFontLoader +{ + /** + * Register a BitmapText font from loader resource. + * + * @param {PIXI.loaders.Resource} resource - Loader resource. + * @param {PIXI.Texture} texture - Reference to texture. + */ + static parse(resource, texture) + { + resource.bitmapFont = BitmapText.registerFont(resource.data, texture); + } + + /** + * Middleware function to support BitmapText fonts, this is automatically installed. + * @see PIXI.loaders.Loader.useMiddleware + */ + static middleware() + { + return function bitmapFontLoader(resource, next) + { + // skip if no data or not xml data + if (!resource.data || resource.type !== Resource.TYPE.XML) + { + next(); + + return; + } + + // skip if not bitmap font data, using some silly duck-typing + if (resource.data.getElementsByTagName('page').length === 0 + || resource.data.getElementsByTagName('info').length === 0 + || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null + ) + { + next(); + + return; + } + + let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; + + if (resource.isDataUrl) + { + if (xmlUrl === '.') + { + xmlUrl = ''; + } + + if (this.baseUrl && xmlUrl) + { + // if baseurl has a trailing slash then add one to xmlUrl so the replace works below + if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') + { + xmlUrl += '/'; + } + } + } + + // remove baseUrl from xmlUrl + xmlUrl = xmlUrl.replace(this.baseUrl, ''); + + // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. + if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') + { + xmlUrl += '/'; + } + + const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); + + if (TextureCache[textureUrl]) + { + // reuse existing texture + BitmapFontLoader.parse(resource, TextureCache[textureUrl]); + next(); + } + else + { + const loadOptions = { + crossOrigin: resource.crossOrigin, + loadType: Resource.LOAD_TYPE.IMAGE, + metadata: resource.metadata.imageMetadata, + parentResource: resource, + }; + + // load the texture for the font + this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => + { + BitmapFontLoader.parse(resource, res.texture); + next(); + }); + } + }; + } +} + +// Add custom add support for loading fnt files as XML +Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); + +// Install loader support for BitmapText +Loader.useMiddleware(BitmapFontLoader.middleware); diff --git a/packages/text-bitmap/src/index.js b/packages/text-bitmap/src/index.js index a98e7b4..e5afd2c 100644 --- a/packages/text-bitmap/src/index.js +++ b/packages/text-bitmap/src/index.js @@ -1 +1,2 @@ export { default as BitmapText } from './BitmapText'; +export { default as BitmapFontLoader } from './BitmapFontLoader'; diff --git a/packages/text-bitmap/test/.eslintrc.json b/packages/text-bitmap/test/.eslintrc.json new file mode 100644 index 0000000..2094b04 --- /dev/null +++ b/packages/text-bitmap/test/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "globals": { + "expect": false, + "assert": false, + "sinon": false, + "PIXI": false + }, + "rules": { + "func-names": 0, + "no-unused-expressions": 0 + } +} \ No newline at end of file diff --git a/packages/text-bitmap/test/BitmapFontLoader.js b/packages/text-bitmap/test/BitmapFontLoader.js new file mode 100644 index 0000000..a76fc1c --- /dev/null +++ b/packages/text-bitmap/test/BitmapFontLoader.js @@ -0,0 +1,319 @@ +const path = require('path'); +const fs = require('fs'); +const { BaseTextureCache, TextureCache } = require('@pixi/utils'); +const { Texture, BaseTexture } = require('@pixi/core'); +const { Spritesheet } = require('@pixi/spritesheet'); +const { BitmapText, BitmapFontLoader } = require('../'); + +describe('PIXI.BitmapFontLoader', function () +{ + afterEach(function () + { + for (const font in BitmapText.fonts) + { + delete BitmapText.fonts[font]; + } + for (const baseTexture in BaseTextureCache) + { + delete BaseTextureCache[baseTexture]; + } + for (const texture in TextureCache) + { + delete TextureCache[texture]; + } + }); + + before(function (done) + { + const resolveURL = (url) => path.resolve(this.resources, url); + + this.resources = path.join(__dirname, 'resources'); + this.fontXML = null; + this.fontScaledXML = null; + this.fontImage = null; + this.fontScaledImage = null; + this.atlasImage = null; + this.atlasScaledImage = null; + this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require + this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require + + const loadXML = (url) => new Promise((resolve) => + fs.readFile(resolveURL(url), 'utf8', (err, data) => + { + expect(err).to.be.null; + resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); + })); + + const loadImage = (url) => new Promise((resolve) => + { + const image = new Image(); + + image.onload = () => resolve(image); + image.src = resolveURL(url); + }); + + Promise.all([ + loadXML('font.fnt'), + loadXML('font@0.5x.fnt'), + loadImage('font.png'), + loadImage('font@0.5x.png'), + loadImage('atlas.png'), + loadImage('atlas@0.5x.png'), + ]).then(([ + fontXML, + fontScaledXML, + fontImage, + fontScaledImage, + atlasImage, + atlasScaledImage, + ]) => + { + this.fontXML = fontXML; + this.fontScaledXML = fontScaledXML; + this.fontImage = fontImage; + this.fontScaledImage = fontScaledImage; + this.atlasImage = atlasImage; + this.atlasScaledImage = atlasScaledImage; + done(); + }); + }); + + it('should exist and return a function', function () + { + expect(BitmapFontLoader.middleware).to.be.a('function'); + expect(BitmapFontLoader.middleware()).to.be.a('function'); + }); + + it('should do nothing if the resource is not XML', function () + { + const spy = sinon.spy(); + const res = {}; + + BitmapFontLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should do nothing if the resource is not properly formatted XML', function () + { + const spy = sinon.spy(); + const res = { data: document.createDocumentFragment() }; + + BitmapFontLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + // TODO: Test the texture cache code path. + // TODO: Test the loading texture code path. + // TODO: Test data-url code paths. + + it('should properly register bitmap font', function (done) + { + const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); + 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charA.texture.frame.x).to.equal(2); + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charB.texture.frame.x).to.equal(2); + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charC.texture.frame.x).to.equal(23); + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charD.texture.frame.x).to.equal(19); + 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]; + + expect(charE).to.be.undefined; + done(); + }); + + it('should properly register SCALED bitmap font', function (done) + { + const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); + const font = BitmapText.registerFont(this.fontScaledXML, 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 + 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]; + + expect(charE).to.be.undefined; + done(); + }); + + it('should properly register bitmap font NESTED into spritesheet', function (done) + { + const baseTexture = new BaseTexture(this.atlasImage, null, 1); + const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); + + spritesheet.parse(() => + { + const fontTexture = Texture.fromFrame('resources/font.png'); + const font = BitmapText.registerFont(this.fontXML, fontTexture); + const fontX = 158; // bare value from spritesheet frame + const fontY = 2; // bare value from spritesheet frame + + 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charA.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charB.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charC.texture.frame.x).to.equal(fontX + 23); + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charD.texture.frame.x).to.equal(fontX + 19); + 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]; + + expect(charE).to.be.undefined; + done(); + }); + }); + + it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) + { + const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); + const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); + + spritesheet.resolution = 1; + + spritesheet.parse(() => + { + const fontTexture = Texture.fromFrame('resources/font.png'); + const font = BitmapText.registerFont(this.fontXML, fontTexture); + const fontX = 158; // bare value from spritesheet frame + const fontY = 2; // bare value from spritesheet frame + + 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charA.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charB.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charC.texture.frame.x).to.equal(fontX + 23); + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charD.texture.frame.x).to.equal(fontX + 19); + 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]; + + expect(charE).to.be.undefined; + done(); + }); + }); + + it('should parse exist', function () + { + expect(BitmapFontLoader.parse).to.be.a('function'); + }); +}); diff --git a/packages/text-bitmap/test/index.js b/packages/text-bitmap/test/index.js new file mode 100644 index 0000000..688fee2 --- /dev/null +++ b/packages/text-bitmap/test/index.js @@ -0,0 +1 @@ +require('./BitmapFontLoader'); diff --git a/packages/text-bitmap/test/resources/atlas.json b/packages/text-bitmap/test/resources/atlas.json new file mode 100644 index 0000000..86e65a5 --- /dev/null +++ b/packages/text-bitmap/test/resources/atlas.json @@ -0,0 +1,25 @@ +{ + "meta": { + "image": "atlas.png", + "size": {"w":256,"h":256}, + "scale": "1" + }, + "frames": { + "resources/test.png": + { + "frame": {"x":2,"y":2,"w":152,"h":188}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, + "sourceSize": {"w":300,"h":225} + }, + "resources/font.png": + { + "frame": {"x":158,"y":2,"w":40,"h":42}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, + "sourceSize": {"w":43,"h":46} + } + } +} \ No newline at end of file diff --git a/packages/text-bitmap/test/resources/atlas.png b/packages/text-bitmap/test/resources/atlas.png new file mode 100644 index 0000000..d5e7892 --- /dev/null +++ b/packages/text-bitmap/test/resources/atlas.png Binary files differ diff --git a/packages/text-bitmap/test/resources/atlas@0.5x.json b/packages/text-bitmap/test/resources/atlas@0.5x.json new file mode 100644 index 0000000..ae990a1 --- /dev/null +++ b/packages/text-bitmap/test/resources/atlas@0.5x.json @@ -0,0 +1,25 @@ +{ + "meta": { + "image": "atlas@0.5x.png", + "size": {"w":256,"h":256}, + "scale": "1" + }, + "frames": { + "resources/test.png": + { + "frame": {"x":2,"y":2,"w":152,"h":188}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, + "sourceSize": {"w":300,"h":225} + }, + "resources/font.png": + { + "frame": {"x":158,"y":2,"w":40,"h":42}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, + "sourceSize": {"w":43,"h":46} + } + } +} \ No newline at end of file diff --git a/packages/text-bitmap/test/resources/atlas@0.5x.png b/packages/text-bitmap/test/resources/atlas@0.5x.png new file mode 100644 index 0000000..d5e7892 --- /dev/null +++ b/packages/text-bitmap/test/resources/atlas@0.5x.png Binary files differ diff --git a/packages/text-bitmap/test/resources/font.fnt b/packages/text-bitmap/test/resources/font.fnt new file mode 100644 index 0000000..56e1060 --- /dev/null +++ b/packages/text-bitmap/test/resources/font.fnt @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/packages/loaders/src/spritesheetParser.js b/packages/loaders/src/spritesheetParser.js deleted file mode 100644 index f7fa206..0000000 --- a/packages/loaders/src/spritesheetParser.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Resource } from 'resource-loader'; -import url from 'url'; -import { Spritesheet } from '@pixi/spritesheet'; - -export default function () -{ - return function spritesheetParser(resource, next) - { - const imageResourceName = `${resource.name}_image`; - - // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists - if (!resource.data - || resource.type !== Resource.TYPE.JSON - || !resource.data.frames - || this.resources[imageResourceName] - ) - { - next(); - - return; - } - - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - const resourcePath = getResourcePath(resource, this.baseUrl); - - // load the image for this sheet - this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) - { - const spritesheet = new Spritesheet( - res.texture.baseTexture, - resource.data, - resource.url - ); - - spritesheet.parse(() => - { - resource.spritesheet = spritesheet; - resource.textures = spritesheet.textures; - next(); - }); - }); - }; -} - -export function getResourcePath(resource, baseUrl) -{ - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - return resource.data.meta.image; - } - - return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); -} diff --git a/packages/loaders/test/bitmapFontParser.js b/packages/loaders/test/bitmapFontParser.js deleted file mode 100644 index 43ef194..0000000 --- a/packages/loaders/test/bitmapFontParser.js +++ /dev/null @@ -1,325 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const { BitmapText } = require('@pixi/text-bitmap'); -const { BaseTextureCache, TextureCache } = require('@pixi/utils'); -const { Texture, BaseTexture } = require('@pixi/core'); -const { Spritesheet } = require('@pixi/spritesheet'); -const { bitmapFontParser, parseBitmapFontData } = require('../'); - -describe('PIXI.loaders.bitmapFontParser', function () -{ - afterEach(function () - { - for (const font in BitmapText.fonts) - { - delete BitmapText.fonts[font]; - } - for (const baseTexture in BaseTextureCache) - { - delete BaseTextureCache[baseTexture]; - } - for (const texture in TextureCache) - { - delete TextureCache[texture]; - } - }); - - before(function (done) - { - const resolveURL = (url) => path.resolve(this.resources, url); - - this.resources = path.join(__dirname, 'resources'); - this.fontXML = null; - this.fontScaledXML = null; - this.fontImage = null; - this.fontScaledImage = null; - this.atlasImage = null; - this.atlasScaledImage = null; - this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require - this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require - - const loadXML = (url) => new Promise((resolve) => - fs.readFile(resolveURL(url), 'utf8', (err, data) => - { - expect(err).to.be.null; - resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); - })); - - const loadImage = (url) => new Promise((resolve) => - { - const image = new Image(); - - image.onload = () => resolve(image); - image.src = resolveURL(url); - }); - - Promise.all([ - loadXML('font.fnt'), - loadXML('font@0.5x.fnt'), - loadImage('font.png'), - loadImage('font@0.5x.png'), - loadImage('atlas.png'), - loadImage('atlas@0.5x.png'), - ]).then(([ - fontXML, - fontScaledXML, - fontImage, - fontScaledImage, - atlasImage, - atlasScaledImage, - ]) => - { - this.fontXML = fontXML; - this.fontScaledXML = fontScaledXML; - this.fontImage = fontImage; - this.fontScaledImage = fontScaledImage; - this.atlasImage = atlasImage; - this.atlasScaledImage = atlasScaledImage; - done(); - }); - }); - - it('should exist and return a function', function () - { - expect(bitmapFontParser).to.be.a('function'); - expect(bitmapFontParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not XML', function () - { - const spy = sinon.spy(); - const res = {}; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is not properly formatted XML', function () - { - const spy = sinon.spy(); - const res = { data: document.createDocumentFragment() }; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - // TODO: Test the texture cache code path. - // TODO: Test the loading texture code path. - // TODO: Test data-url code paths. - - it('should properly register bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charA.texture.frame.x).to.equal(2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charB.texture.frame.x).to.equal(2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charC.texture.frame.x).to.equal(23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charD.texture.frame.x).to.equal(19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register SCALED bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); - const font = BitmapText.registerFont(this.fontScaledXML, 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register bitmap font NESTED into spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); - - it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); - - spritesheet.resolution = 1; - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); -}); - -describe('PIXI.loaders.parseBitmapFontData', function () -{ - it('should exist', function () - { - expect(parseBitmapFontData).to.be.a('function'); - }); - - // TODO: Test the parser code. -}); diff --git a/packages/loaders/test/index.js b/packages/loaders/test/index.js index f00de5f..72c6554 100644 --- a/packages/loaders/test/index.js +++ b/packages/loaders/test/index.js @@ -1,4 +1,2 @@ -require('./bitmapFontParser'); require('./loader'); -require('./spritesheetParser'); require('./textureParser'); diff --git a/packages/loaders/test/resources/atlas.json b/packages/loaders/test/resources/atlas.json deleted file mode 100644 index 86e65a5..0000000 --- a/packages/loaders/test/resources/atlas.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas.png b/packages/loaders/test/resources/atlas.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/atlas@0.5x.json b/packages/loaders/test/resources/atlas@0.5x.json deleted file mode 100644 index ae990a1..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas@0.5x.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas@0.5x.png b/packages/loaders/test/resources/atlas@0.5x.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font.fnt b/packages/loaders/test/resources/font.fnt deleted file mode 100644 index 56e1060..0000000 --- a/packages/loaders/test/resources/font.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font.png b/packages/loaders/test/resources/font.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font@0.5x.fnt b/packages/loaders/test/resources/font@0.5x.fnt deleted file mode 100644 index 6c247c7..0000000 --- a/packages/loaders/test/resources/font@0.5x.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font@0.5x.png b/packages/loaders/test/resources/font@0.5x.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/spritesheetParser.js b/packages/loaders/test/spritesheetParser.js deleted file mode 100644 index 98890b8..0000000 --- a/packages/loaders/test/spritesheetParser.js +++ /dev/null @@ -1,213 +0,0 @@ -const path = require('path'); -const { spritesheetParser, Resource, Loader, getResourcePath } = require('../'); -const { Texture, BaseTexture } = require('@pixi/core'); - -describe('PIXI.loaders.spritesheetParser', function () -{ - it('should exist and return a function', function () - { - expect(spritesheetParser).to.be.a('function'); - expect(spritesheetParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not JSON', function () - { - const spy = sinon.spy(); - const res = {}; - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is JSON, but improper format', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, {}); - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should load the image & create textures if json is properly formatted', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); - const loader = new Loader(); - const addStub = sinon.stub(loader, 'add'); - const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); - - imgRes.texture = new Texture(new BaseTexture(imgRes.data)); - - addStub.yields(imgRes); - - spritesheetParser().call(loader, res, spy); - - addStub.restore(); - - expect(spy).to.have.been.calledOnce; - expect(addStub).to.have.been.calledWith( - `${res.name}_image`, - `${path.dirname(res.url)}/${res.data.meta.image}` - ); - expect(res).to.have.property('textures') - .that.is.an('object') - .with.keys(Object.keys(getJsonSpritesheet().frames)) - .and.has.property('0.png') - .that.is.an.instanceof(Texture); - }); - - it('should build the image url', function () - { - function getPath(url, image) - { - return getResourcePath({ - url, - data: { meta: { image } }, - }); - } - - let result = getPath('http://some.com/spritesheet.json', 'img.png'); - - expect(result).to.be.equals('http://some.com/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('http://some.com/some/img.png'); - - result = getPath('/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/img.png'); - - result = getPath('/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('/some/img.png'); - }); - - // TODO: Test that rectangles are created correctly. - // TODO: Test that bathc processing works correctly. - // TODO: Test that resolution processing works correctly. - // TODO: Test that metadata is honored. -}); - -function createMockResource(type, data) -{ - const name = `${Math.floor(Date.now() * Math.random())}`; - - return { - url: `http://localhost/doesnt_exist/${name}`, - name, - type, - data, - metadata: {}, - }; -} - -function getJsonSpritesheet() -{ - /* eslint-disable */ - return {"frames": { - "0.png": - { - "frame": {"x":14,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "1.png": - { - "frame": {"x":14,"y":42,"w":12,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, - "sourceSize": {"w":12,"h":14} - }, - "2.png": - { - "frame": {"x":14,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "3.png": - { - "frame": {"x":42,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "4.png": - { - "frame": {"x":28,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "5.png": - { - "frame": {"x":14,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "6.png": - { - "frame": {"x":0,"y":42,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "7.png": - { - "frame": {"x":0,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "8.png": - { - "frame": {"x":0,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "9.png": - { - "frame": {"x":0,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }}, - "meta": { - "app": "http://www.texturepacker.com", - "version": "1.0", - "image": "hud.png", - "format": "RGBA8888", - "size": {"w":64,"h":64}, - "scale": "1", - "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" - } - }; - /* eslint-enable */ -} diff --git a/packages/mixin-app-loader/LICENSE b/packages/mixin-app-loader/LICENSE new file mode 100644 index 0000000..5d16b50 --- /dev/null +++ b/packages/mixin-app-loader/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2013-2017 Mathew Groves, Chad Engler + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/packages/mixin-app-loader/README.md b/packages/mixin-app-loader/README.md new file mode 100644 index 0000000..3e89f86 --- /dev/null +++ b/packages/mixin-app-loader/README.md @@ -0,0 +1,13 @@ +# @pixi/mixin-app-loader + +## Installation + +```bash +npm install @pixi/mixin-app-loader +``` + +## Usage + +```js +import '@pixi/mixin-app-loader'; +``` \ No newline at end of file diff --git a/packages/mixin-app-loader/package.json b/packages/mixin-app-loader/package.json new file mode 100644 index 0000000..869ed4e --- /dev/null +++ b/packages/mixin-app-loader/package.json @@ -0,0 +1,41 @@ +{ + "name": "@pixi/mixin-app-loader", + "version": "5.0.0-alpha", + "main": "lib/mixin-app-loader.js", + "module": "lib/mixin-app-loader.es.js", + "description": "Support for loader in Application", + "author": "Mat Groves", + "contributors": [ + "Matt Karl " + ], + "homepage": "http://pixijs.com/", + "bugs": "https://github.com/pixijs/pixi.js/issues", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/pixijs/pixi.js.git" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "build:dev": "rollup -c", + "build": "rollup -cp", + "watch": "rollup -cw", + "postversion": "npm run build", + "test": "tester" + }, + "files": [ + "lib" + ], + "dependencies": { + "@pixi/app": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" + }, + "devDependencies": { + "@pixi/utils": "^5.0.0-alpha", + "@internal/tester": "^5.0.0-alpha", + "@internal/builder": "^5.0.0-alpha", + "rollup": "^0.50.0" + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/rollup.config.js b/packages/mixin-app-loader/rollup.config.js new file mode 100644 index 0000000..a73c874 --- /dev/null +++ b/packages/mixin-app-loader/rollup.config.js @@ -0,0 +1 @@ +export { default } from '@internal/builder'; diff --git a/packages/mixin-app-loader/src/index.js b/packages/mixin-app-loader/src/index.js new file mode 100644 index 0000000..354997a --- /dev/null +++ b/packages/mixin-app-loader/src/index.js @@ -0,0 +1,38 @@ +import { Application } from '@pixi/app'; +import { Loader, shared } from '@pixi/loaders'; + +Application.prototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperties(Application.prototype, { + loader: { + get() + { + if (!this._loader && this._options) + { + const { sharedLoader } = this._options; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +Application.prototype._parentDestroy = Application.prototype.destroy; +Application.prototype.destroy = function destroy(removeView) +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(removeView); +}; diff --git a/packages/mixin-app-loader/test/.eslintrc.json b/packages/mixin-app-loader/test/.eslintrc.json new file mode 100644 index 0000000..2094b04 --- /dev/null +++ b/packages/mixin-app-loader/test/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "globals": { + "expect": false, + "assert": false, + "sinon": false, + "PIXI": false + }, + "rules": { + "func-names": 0, + "no-unused-expressions": 0 + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/test/index.js b/packages/mixin-app-loader/test/index.js new file mode 100644 index 0000000..3468fe5 --- /dev/null +++ b/packages/mixin-app-loader/test/index.js @@ -0,0 +1,35 @@ +const { Application } = require('@pixi/app'); +const { Loader, shared } = require('@pixi/loaders'); +const { skipHello } = require('@pixi/utils'); + +require('../'); + +skipHello(); + +describe('PIXI.Application#loader', function () +{ + it('should contain loader property', function () + { + const obj = new Application(); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); + + it('should use sharedLoader option', function () + { + const obj = new Application({ sharedLoader: true }); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + expect(obj.loader).to.equal(shared); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); +}); diff --git a/packages/spritesheet/package.json b/packages/spritesheet/package.json index 34749e2..5f35442 100644 --- a/packages/spritesheet/package.json +++ b/packages/spritesheet/package.json @@ -31,7 +31,8 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/math": "^5.0.0-alpha", - "@pixi/utils": "^5.0.0-alpha" + "@pixi/utils": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" }, "devDependencies": { "@internal/builder": "^5.0.0-alpha", diff --git a/packages/spritesheet/src/SpritesheetLoader.js b/packages/spritesheet/src/SpritesheetLoader.js new file mode 100644 index 0000000..c7b34ea --- /dev/null +++ b/packages/spritesheet/src/SpritesheetLoader.js @@ -0,0 +1,83 @@ +import url from 'url'; +import { Resource, Loader } from '@pixi/loaders'; +import Spritesheet from './Spritesheet'; + +/** + * {@link PIXI.loaders.Loader Loader} middleware for loading + * texture atlases that have been created with TexturePacker or + * similar JSON-based spritesheet. This automatically generates + * Texture resources. + * @class + * @memberof PIXI + */ +export default class SpritesheetLoader +{ + /** + * Middleware function to support Spritesheets, this is automatically installed. + * @see PIXI.loaders.Loader.useMiddleware + */ + static middleware() + { + return function spritesheetLoader(resource, next) + { + const imageResourceName = `${resource.name}_image`; + + // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists + if (!resource.data + || resource.type !== Resource.TYPE.JSON + || !resource.data.frames + || this.resources[imageResourceName] + ) + { + next(); + + return; + } + + const loadOptions = { + crossOrigin: resource.crossOrigin, + loadType: Resource.LOAD_TYPE.IMAGE, + metadata: resource.metadata.imageMetadata, + parentResource: resource, + }; + + const resourcePath = SpritesheetLoader.getResourcePath(resource, this.baseUrl); + + // load the image for this sheet + this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) + { + const spritesheet = new Spritesheet( + res.texture.baseTexture, + resource.data, + resource.url + ); + + spritesheet.parse(() => + { + resource.spritesheet = spritesheet; + resource.textures = spritesheet.textures; + next(); + }); + }); + }; + } + + /** + * Get the spritesheets root path + * @param {PIXI.loader.Resource} resource - Resource to check path + * @param {string} baseUrl - Base root url + */ + static getResourcePath(resource, baseUrl) + { + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); + } +} + +// Install loader support for Spritesheet objects +Loader.useMiddleware(SpritesheetLoader.middleware); diff --git a/packages/spritesheet/src/index.js b/packages/spritesheet/src/index.js index 51d96a1..b9ee1e8 100644 --- a/packages/spritesheet/src/index.js +++ b/packages/spritesheet/src/index.js @@ -1 +1,2 @@ export { default as Spritesheet } from './Spritesheet'; +export { default as SpritesheetLoader } from './SpritesheetLoader'; diff --git a/packages/spritesheet/test/Spritesheet.js b/packages/spritesheet/test/Spritesheet.js new file mode 100644 index 0000000..35d8688 --- /dev/null +++ b/packages/spritesheet/test/Spritesheet.js @@ -0,0 +1,105 @@ +const { Spritesheet } = require('../'); +const { BaseTexture, Texture } = require('@pixi/core'); +const path = require('path'); + +describe('PIXI.Spritesheet', function () +{ + before(function () + { + this.resources = path.join(__dirname, 'resources'); + this.validate = function (spritesheet, done) + { + spritesheet.parse(function (textures) + { + const id = 'goldmine_10_5.png'; + const width = Math.floor(spritesheet.data.frames[id].frame.w); + const height = Math.floor(spritesheet.data.frames[id].frame.h); + + expect(Object.keys(textures).length).to.equal(1); + expect(Object.keys(spritesheet.textures).length).to.equal(1); + expect(textures[id]).to.be.an.instanceof(Texture); + expect(textures[id].width).to.equal(width / spritesheet.resolution); + expect(textures[id].height).to.equal(height / spritesheet.resolution); + expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); + spritesheet.destroy(true); + expect(spritesheet.textures).to.be.null; + expect(spritesheet.baseTexture).to.be.null; + done(); + }); + }; + }); + + it('should exist on PIXI', function () + { + expect(Spritesheet).to.be.a.function; + expect(Spritesheet.BATCH_SIZE).to.be.a.number; + }); + + it('should create an instance', function () + { + const baseTexture = new BaseTexture(); + const data = { + frames: {}, + meta: {}, + }; + + const spritesheet = new Spritesheet(baseTexture, data); + + expect(spritesheet.data).to.equal(data); + expect(spritesheet.baseTexture).to.equal(baseTexture); + expect(spritesheet.resolution).to.equal(1); + + spritesheet.destroy(true); + }); + + it('should create instance with scale resolution', function (done) + { + const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require + const image = new Image(); + + image.src = path.join(this.resources, data.meta.image); + image.onload = () => + { + const baseTexture = new BaseTexture(image, null, 1); + const spritesheet = new Spritesheet(baseTexture, data); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1.png'); + expect(spritesheet.resolution).to.equal(0.5); + this.validate(spritesheet, done); + }; + }); + + it('should create instance with BaseTexture source scale', function (done) + { + const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require + const baseTexture = new BaseTexture.fromImage(data.meta.image, undefined, undefined, 1.5); + const spritesheet = new Spritesheet(baseTexture, data); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1.png'); + expect(spritesheet.resolution).to.equal(0.5); + + this.validate(spritesheet, done); + }); + + it('should create instance with filename resolution', function (done) + { + const uri = path.resolve(this.resources, 'building1@2x.json'); + const data = require(uri); // eslint-disable-line global-require + const image = new Image(); + + image.src = path.join(this.resources, data.meta.image); + image.onload = () => + { + const baseTexture = new BaseTexture(image, null, 1); + const spritesheet = new Spritesheet(baseTexture, data, uri); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1@2x.png'); + expect(spritesheet.resolution).to.equal(2); + + this.validate(spritesheet, done); + }; + }); +}); diff --git a/packages/spritesheet/test/SpritesheetLoader.js b/packages/spritesheet/test/SpritesheetLoader.js new file mode 100644 index 0000000..e4205ec --- /dev/null +++ b/packages/spritesheet/test/SpritesheetLoader.js @@ -0,0 +1,214 @@ +const path = require('path'); +const { Loader, Resource } = require('@pixi/loaders'); +const { Texture, BaseTexture } = require('@pixi/core'); +const { SpritesheetLoader } = require('../'); + +describe('PIXI.SpritesheetLoader', function () +{ + it('should exist and return a function', function () + { + expect(SpritesheetLoader.middleware).to.be.a('function'); + expect(SpritesheetLoader.middleware()).to.be.a('function'); + }); + + it('should do nothing if the resource is not JSON', function () + { + const spy = sinon.spy(); + const res = {}; + + SpritesheetLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should do nothing if the resource is JSON, but improper format', function () + { + const spy = sinon.spy(); + const res = createMockResource(Resource.TYPE.JSON, {}); + + SpritesheetLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should load the image & create textures if json is properly formatted', function () + { + const spy = sinon.spy(); + const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); + const loader = new Loader(); + const addStub = sinon.stub(loader, 'add'); + const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); + + imgRes.texture = new Texture(new BaseTexture(imgRes.data)); + + addStub.yields(imgRes); + + SpritesheetLoader.middleware().call(loader, res, spy); + + addStub.restore(); + + expect(spy).to.have.been.calledOnce; + expect(addStub).to.have.been.calledWith( + `${res.name}_image`, + `${path.dirname(res.url)}/${res.data.meta.image}` + ); + expect(res).to.have.property('textures') + .that.is.an('object') + .with.keys(Object.keys(getJsonSpritesheet().frames)) + .and.has.property('0.png') + .that.is.an.instanceof(Texture); + }); + + it('should build the image url', function () + { + function getPath(url, image) + { + return SpritesheetLoader.getResourcePath({ + url, + data: { meta: { image } }, + }); + } + + let result = getPath('http://some.com/spritesheet.json', 'img.png'); + + expect(result).to.be.equals('http://some.com/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); + expect(result).to.be.equals('http://some.com/some/dir/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); + expect(result).to.be.equals('http://some.com/some/dir/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); + expect(result).to.be.equals('http://some.com/some/img.png'); + + result = getPath('/spritesheet.json', 'img.png'); + expect(result).to.be.equals('/img.png'); + + result = getPath('/some/dir/spritesheet.json', 'img.png'); + expect(result).to.be.equals('/some/dir/img.png'); + + result = getPath('/some/dir/spritesheet.json', './img.png'); + expect(result).to.be.equals('/some/dir/img.png'); + + result = getPath('/some/dir/spritesheet.json', '../img.png'); + expect(result).to.be.equals('/some/img.png'); + }); + + // TODO: Test that rectangles are created correctly. + // TODO: Test that bathc processing works correctly. + // TODO: Test that resolution processing works correctly. + // TODO: Test that metadata is honored. +}); + +function createMockResource(type, data) +{ + const name = `${Math.floor(Date.now() * Math.random())}`; + + return { + url: `http://localhost/doesnt_exist/${name}`, + name, + type, + data, + metadata: {}, + }; +} + +function getJsonSpritesheet() +{ + /* eslint-disable */ + return {"frames": { + "0.png": + { + "frame": {"x":14,"y":28,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "1.png": + { + "frame": {"x":14,"y":42,"w":12,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, + "sourceSize": {"w":12,"h":14} + }, + "2.png": + { + "frame": {"x":14,"y":14,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "3.png": + { + "frame": {"x":42,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "4.png": + { + "frame": {"x":28,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "5.png": + { + "frame": {"x":14,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "6.png": + { + "frame": {"x":0,"y":42,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "7.png": + { + "frame": {"x":0,"y":28,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "8.png": + { + "frame": {"x":0,"y":14,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "9.png": + { + "frame": {"x":0,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }}, + "meta": { + "app": "http://www.texturepacker.com", + "version": "1.0", + "image": "hud.png", + "format": "RGBA8888", + "size": {"w":64,"h":64}, + "scale": "1", + "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" + } + }; + /* eslint-enable */ +} diff --git a/packages/spritesheet/test/index.js b/packages/spritesheet/test/index.js index 35d8688..8cf5636 100644 --- a/packages/spritesheet/test/index.js +++ b/packages/spritesheet/test/index.js @@ -1,105 +1,2 @@ -const { Spritesheet } = require('../'); -const { BaseTexture, Texture } = require('@pixi/core'); -const path = require('path'); - -describe('PIXI.Spritesheet', function () -{ - before(function () - { - this.resources = path.join(__dirname, 'resources'); - this.validate = function (spritesheet, done) - { - spritesheet.parse(function (textures) - { - const id = 'goldmine_10_5.png'; - const width = Math.floor(spritesheet.data.frames[id].frame.w); - const height = Math.floor(spritesheet.data.frames[id].frame.h); - - expect(Object.keys(textures).length).to.equal(1); - expect(Object.keys(spritesheet.textures).length).to.equal(1); - expect(textures[id]).to.be.an.instanceof(Texture); - expect(textures[id].width).to.equal(width / spritesheet.resolution); - expect(textures[id].height).to.equal(height / spritesheet.resolution); - expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); - spritesheet.destroy(true); - expect(spritesheet.textures).to.be.null; - expect(spritesheet.baseTexture).to.be.null; - done(); - }); - }; - }); - - it('should exist on PIXI', function () - { - expect(Spritesheet).to.be.a.function; - expect(Spritesheet.BATCH_SIZE).to.be.a.number; - }); - - it('should create an instance', function () - { - const baseTexture = new BaseTexture(); - const data = { - frames: {}, - meta: {}, - }; - - const spritesheet = new Spritesheet(baseTexture, data); - - expect(spritesheet.data).to.equal(data); - expect(spritesheet.baseTexture).to.equal(baseTexture); - expect(spritesheet.resolution).to.equal(1); - - spritesheet.destroy(true); - }); - - it('should create instance with scale resolution', function (done) - { - const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require - const image = new Image(); - - image.src = path.join(this.resources, data.meta.image); - image.onload = () => - { - const baseTexture = new BaseTexture(image, null, 1); - const spritesheet = new Spritesheet(baseTexture, data); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1.png'); - expect(spritesheet.resolution).to.equal(0.5); - this.validate(spritesheet, done); - }; - }); - - it('should create instance with BaseTexture source scale', function (done) - { - const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require - const baseTexture = new BaseTexture.fromImage(data.meta.image, undefined, undefined, 1.5); - const spritesheet = new Spritesheet(baseTexture, data); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1.png'); - expect(spritesheet.resolution).to.equal(0.5); - - this.validate(spritesheet, done); - }); - - it('should create instance with filename resolution', function (done) - { - const uri = path.resolve(this.resources, 'building1@2x.json'); - const data = require(uri); // eslint-disable-line global-require - const image = new Image(); - - image.src = path.join(this.resources, data.meta.image); - image.onload = () => - { - const baseTexture = new BaseTexture(image, null, 1); - const spritesheet = new Spritesheet(baseTexture, data, uri); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1@2x.png'); - expect(spritesheet.resolution).to.equal(2); - - this.validate(spritesheet, done); - }; - }); -}); +require('./Spritesheet'); +require('./SpritesheetLoader'); diff --git a/packages/text-bitmap/package.json b/packages/text-bitmap/package.json index a8a5f95..4b47da6 100644 --- a/packages/text-bitmap/package.json +++ b/packages/text-bitmap/package.json @@ -23,7 +23,7 @@ "build": "rollup -cp", "watch": "rollup -cw", "postversion": "npm run build", - "test": "exit 0" + "test": "tester" }, "files": [ "lib" @@ -31,12 +31,15 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha", "@pixi/sprite": "^5.0.0-alpha", "@pixi/settings": "^5.0.0-alpha", "@pixi/math": "^5.0.0-alpha", "@pixi/display": "^5.0.0-alpha" }, "devDependencies": { + "@pixi/spritesheet": "^5.0.0-alpha", + "@internal/tester": "^5.0.0-alpha", "@internal/builder": "^5.0.0-alpha", "rollup": "^0.50.0" } diff --git a/packages/text-bitmap/src/BitmapFontLoader.js b/packages/text-bitmap/src/BitmapFontLoader.js new file mode 100644 index 0000000..3956060 --- /dev/null +++ b/packages/text-bitmap/src/BitmapFontLoader.js @@ -0,0 +1,112 @@ +import * as path from 'path'; +import { TextureCache } from '@pixi/utils'; +import { Resource, Loader } from '@pixi/loaders'; +import BitmapText from './BitmapText'; + +/** + * {@link PIXI.loaders.Loader Loader} middleware for loading + * bitmap-based fonts suitable for using with {@link PIXI.BitmapText}. + * @class + * @memberof PIXI + */ +export default class BitmapFontLoader +{ + /** + * Register a BitmapText font from loader resource. + * + * @param {PIXI.loaders.Resource} resource - Loader resource. + * @param {PIXI.Texture} texture - Reference to texture. + */ + static parse(resource, texture) + { + resource.bitmapFont = BitmapText.registerFont(resource.data, texture); + } + + /** + * Middleware function to support BitmapText fonts, this is automatically installed. + * @see PIXI.loaders.Loader.useMiddleware + */ + static middleware() + { + return function bitmapFontLoader(resource, next) + { + // skip if no data or not xml data + if (!resource.data || resource.type !== Resource.TYPE.XML) + { + next(); + + return; + } + + // skip if not bitmap font data, using some silly duck-typing + if (resource.data.getElementsByTagName('page').length === 0 + || resource.data.getElementsByTagName('info').length === 0 + || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null + ) + { + next(); + + return; + } + + let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; + + if (resource.isDataUrl) + { + if (xmlUrl === '.') + { + xmlUrl = ''; + } + + if (this.baseUrl && xmlUrl) + { + // if baseurl has a trailing slash then add one to xmlUrl so the replace works below + if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') + { + xmlUrl += '/'; + } + } + } + + // remove baseUrl from xmlUrl + xmlUrl = xmlUrl.replace(this.baseUrl, ''); + + // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. + if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') + { + xmlUrl += '/'; + } + + const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); + + if (TextureCache[textureUrl]) + { + // reuse existing texture + BitmapFontLoader.parse(resource, TextureCache[textureUrl]); + next(); + } + else + { + const loadOptions = { + crossOrigin: resource.crossOrigin, + loadType: Resource.LOAD_TYPE.IMAGE, + metadata: resource.metadata.imageMetadata, + parentResource: resource, + }; + + // load the texture for the font + this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => + { + BitmapFontLoader.parse(resource, res.texture); + next(); + }); + } + }; + } +} + +// Add custom add support for loading fnt files as XML +Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); + +// Install loader support for BitmapText +Loader.useMiddleware(BitmapFontLoader.middleware); diff --git a/packages/text-bitmap/src/index.js b/packages/text-bitmap/src/index.js index a98e7b4..e5afd2c 100644 --- a/packages/text-bitmap/src/index.js +++ b/packages/text-bitmap/src/index.js @@ -1 +1,2 @@ export { default as BitmapText } from './BitmapText'; +export { default as BitmapFontLoader } from './BitmapFontLoader'; diff --git a/packages/text-bitmap/test/.eslintrc.json b/packages/text-bitmap/test/.eslintrc.json new file mode 100644 index 0000000..2094b04 --- /dev/null +++ b/packages/text-bitmap/test/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "globals": { + "expect": false, + "assert": false, + "sinon": false, + "PIXI": false + }, + "rules": { + "func-names": 0, + "no-unused-expressions": 0 + } +} \ No newline at end of file diff --git a/packages/text-bitmap/test/BitmapFontLoader.js b/packages/text-bitmap/test/BitmapFontLoader.js new file mode 100644 index 0000000..a76fc1c --- /dev/null +++ b/packages/text-bitmap/test/BitmapFontLoader.js @@ -0,0 +1,319 @@ +const path = require('path'); +const fs = require('fs'); +const { BaseTextureCache, TextureCache } = require('@pixi/utils'); +const { Texture, BaseTexture } = require('@pixi/core'); +const { Spritesheet } = require('@pixi/spritesheet'); +const { BitmapText, BitmapFontLoader } = require('../'); + +describe('PIXI.BitmapFontLoader', function () +{ + afterEach(function () + { + for (const font in BitmapText.fonts) + { + delete BitmapText.fonts[font]; + } + for (const baseTexture in BaseTextureCache) + { + delete BaseTextureCache[baseTexture]; + } + for (const texture in TextureCache) + { + delete TextureCache[texture]; + } + }); + + before(function (done) + { + const resolveURL = (url) => path.resolve(this.resources, url); + + this.resources = path.join(__dirname, 'resources'); + this.fontXML = null; + this.fontScaledXML = null; + this.fontImage = null; + this.fontScaledImage = null; + this.atlasImage = null; + this.atlasScaledImage = null; + this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require + this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require + + const loadXML = (url) => new Promise((resolve) => + fs.readFile(resolveURL(url), 'utf8', (err, data) => + { + expect(err).to.be.null; + resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); + })); + + const loadImage = (url) => new Promise((resolve) => + { + const image = new Image(); + + image.onload = () => resolve(image); + image.src = resolveURL(url); + }); + + Promise.all([ + loadXML('font.fnt'), + loadXML('font@0.5x.fnt'), + loadImage('font.png'), + loadImage('font@0.5x.png'), + loadImage('atlas.png'), + loadImage('atlas@0.5x.png'), + ]).then(([ + fontXML, + fontScaledXML, + fontImage, + fontScaledImage, + atlasImage, + atlasScaledImage, + ]) => + { + this.fontXML = fontXML; + this.fontScaledXML = fontScaledXML; + this.fontImage = fontImage; + this.fontScaledImage = fontScaledImage; + this.atlasImage = atlasImage; + this.atlasScaledImage = atlasScaledImage; + done(); + }); + }); + + it('should exist and return a function', function () + { + expect(BitmapFontLoader.middleware).to.be.a('function'); + expect(BitmapFontLoader.middleware()).to.be.a('function'); + }); + + it('should do nothing if the resource is not XML', function () + { + const spy = sinon.spy(); + const res = {}; + + BitmapFontLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should do nothing if the resource is not properly formatted XML', function () + { + const spy = sinon.spy(); + const res = { data: document.createDocumentFragment() }; + + BitmapFontLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + // TODO: Test the texture cache code path. + // TODO: Test the loading texture code path. + // TODO: Test data-url code paths. + + it('should properly register bitmap font', function (done) + { + const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); + 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charA.texture.frame.x).to.equal(2); + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charB.texture.frame.x).to.equal(2); + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charC.texture.frame.x).to.equal(23); + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charD.texture.frame.x).to.equal(19); + 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]; + + expect(charE).to.be.undefined; + done(); + }); + + it('should properly register SCALED bitmap font', function (done) + { + const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); + const font = BitmapText.registerFont(this.fontScaledXML, 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 + 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]; + + expect(charE).to.be.undefined; + done(); + }); + + it('should properly register bitmap font NESTED into spritesheet', function (done) + { + const baseTexture = new BaseTexture(this.atlasImage, null, 1); + const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); + + spritesheet.parse(() => + { + const fontTexture = Texture.fromFrame('resources/font.png'); + const font = BitmapText.registerFont(this.fontXML, fontTexture); + const fontX = 158; // bare value from spritesheet frame + const fontY = 2; // bare value from spritesheet frame + + 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charA.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charB.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charC.texture.frame.x).to.equal(fontX + 23); + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charD.texture.frame.x).to.equal(fontX + 19); + 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]; + + expect(charE).to.be.undefined; + done(); + }); + }); + + it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) + { + const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); + const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); + + spritesheet.resolution = 1; + + spritesheet.parse(() => + { + const fontTexture = Texture.fromFrame('resources/font.png'); + const font = BitmapText.registerFont(this.fontXML, fontTexture); + const fontX = 158; // bare value from spritesheet frame + const fontY = 2; // bare value from spritesheet frame + + 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charA.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charB.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charC.texture.frame.x).to.equal(fontX + 23); + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charD.texture.frame.x).to.equal(fontX + 19); + 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]; + + expect(charE).to.be.undefined; + done(); + }); + }); + + it('should parse exist', function () + { + expect(BitmapFontLoader.parse).to.be.a('function'); + }); +}); diff --git a/packages/text-bitmap/test/index.js b/packages/text-bitmap/test/index.js new file mode 100644 index 0000000..688fee2 --- /dev/null +++ b/packages/text-bitmap/test/index.js @@ -0,0 +1 @@ +require('./BitmapFontLoader'); diff --git a/packages/text-bitmap/test/resources/atlas.json b/packages/text-bitmap/test/resources/atlas.json new file mode 100644 index 0000000..86e65a5 --- /dev/null +++ b/packages/text-bitmap/test/resources/atlas.json @@ -0,0 +1,25 @@ +{ + "meta": { + "image": "atlas.png", + "size": {"w":256,"h":256}, + "scale": "1" + }, + "frames": { + "resources/test.png": + { + "frame": {"x":2,"y":2,"w":152,"h":188}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, + "sourceSize": {"w":300,"h":225} + }, + "resources/font.png": + { + "frame": {"x":158,"y":2,"w":40,"h":42}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, + "sourceSize": {"w":43,"h":46} + } + } +} \ No newline at end of file diff --git a/packages/text-bitmap/test/resources/atlas.png b/packages/text-bitmap/test/resources/atlas.png new file mode 100644 index 0000000..d5e7892 --- /dev/null +++ b/packages/text-bitmap/test/resources/atlas.png Binary files differ diff --git a/packages/text-bitmap/test/resources/atlas@0.5x.json b/packages/text-bitmap/test/resources/atlas@0.5x.json new file mode 100644 index 0000000..ae990a1 --- /dev/null +++ b/packages/text-bitmap/test/resources/atlas@0.5x.json @@ -0,0 +1,25 @@ +{ + "meta": { + "image": "atlas@0.5x.png", + "size": {"w":256,"h":256}, + "scale": "1" + }, + "frames": { + "resources/test.png": + { + "frame": {"x":2,"y":2,"w":152,"h":188}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, + "sourceSize": {"w":300,"h":225} + }, + "resources/font.png": + { + "frame": {"x":158,"y":2,"w":40,"h":42}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, + "sourceSize": {"w":43,"h":46} + } + } +} \ No newline at end of file diff --git a/packages/text-bitmap/test/resources/atlas@0.5x.png b/packages/text-bitmap/test/resources/atlas@0.5x.png new file mode 100644 index 0000000..d5e7892 --- /dev/null +++ b/packages/text-bitmap/test/resources/atlas@0.5x.png Binary files differ diff --git a/packages/text-bitmap/test/resources/font.fnt b/packages/text-bitmap/test/resources/font.fnt new file mode 100644 index 0000000..56e1060 --- /dev/null +++ b/packages/text-bitmap/test/resources/font.fnt @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/text-bitmap/test/resources/font.png b/packages/text-bitmap/test/resources/font.png new file mode 100644 index 0000000..cf772e9 --- /dev/null +++ b/packages/text-bitmap/test/resources/font.png Binary files differ diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/packages/loaders/src/spritesheetParser.js b/packages/loaders/src/spritesheetParser.js deleted file mode 100644 index f7fa206..0000000 --- a/packages/loaders/src/spritesheetParser.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Resource } from 'resource-loader'; -import url from 'url'; -import { Spritesheet } from '@pixi/spritesheet'; - -export default function () -{ - return function spritesheetParser(resource, next) - { - const imageResourceName = `${resource.name}_image`; - - // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists - if (!resource.data - || resource.type !== Resource.TYPE.JSON - || !resource.data.frames - || this.resources[imageResourceName] - ) - { - next(); - - return; - } - - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - const resourcePath = getResourcePath(resource, this.baseUrl); - - // load the image for this sheet - this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) - { - const spritesheet = new Spritesheet( - res.texture.baseTexture, - resource.data, - resource.url - ); - - spritesheet.parse(() => - { - resource.spritesheet = spritesheet; - resource.textures = spritesheet.textures; - next(); - }); - }); - }; -} - -export function getResourcePath(resource, baseUrl) -{ - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - return resource.data.meta.image; - } - - return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); -} diff --git a/packages/loaders/test/bitmapFontParser.js b/packages/loaders/test/bitmapFontParser.js deleted file mode 100644 index 43ef194..0000000 --- a/packages/loaders/test/bitmapFontParser.js +++ /dev/null @@ -1,325 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const { BitmapText } = require('@pixi/text-bitmap'); -const { BaseTextureCache, TextureCache } = require('@pixi/utils'); -const { Texture, BaseTexture } = require('@pixi/core'); -const { Spritesheet } = require('@pixi/spritesheet'); -const { bitmapFontParser, parseBitmapFontData } = require('../'); - -describe('PIXI.loaders.bitmapFontParser', function () -{ - afterEach(function () - { - for (const font in BitmapText.fonts) - { - delete BitmapText.fonts[font]; - } - for (const baseTexture in BaseTextureCache) - { - delete BaseTextureCache[baseTexture]; - } - for (const texture in TextureCache) - { - delete TextureCache[texture]; - } - }); - - before(function (done) - { - const resolveURL = (url) => path.resolve(this.resources, url); - - this.resources = path.join(__dirname, 'resources'); - this.fontXML = null; - this.fontScaledXML = null; - this.fontImage = null; - this.fontScaledImage = null; - this.atlasImage = null; - this.atlasScaledImage = null; - this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require - this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require - - const loadXML = (url) => new Promise((resolve) => - fs.readFile(resolveURL(url), 'utf8', (err, data) => - { - expect(err).to.be.null; - resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); - })); - - const loadImage = (url) => new Promise((resolve) => - { - const image = new Image(); - - image.onload = () => resolve(image); - image.src = resolveURL(url); - }); - - Promise.all([ - loadXML('font.fnt'), - loadXML('font@0.5x.fnt'), - loadImage('font.png'), - loadImage('font@0.5x.png'), - loadImage('atlas.png'), - loadImage('atlas@0.5x.png'), - ]).then(([ - fontXML, - fontScaledXML, - fontImage, - fontScaledImage, - atlasImage, - atlasScaledImage, - ]) => - { - this.fontXML = fontXML; - this.fontScaledXML = fontScaledXML; - this.fontImage = fontImage; - this.fontScaledImage = fontScaledImage; - this.atlasImage = atlasImage; - this.atlasScaledImage = atlasScaledImage; - done(); - }); - }); - - it('should exist and return a function', function () - { - expect(bitmapFontParser).to.be.a('function'); - expect(bitmapFontParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not XML', function () - { - const spy = sinon.spy(); - const res = {}; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is not properly formatted XML', function () - { - const spy = sinon.spy(); - const res = { data: document.createDocumentFragment() }; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - // TODO: Test the texture cache code path. - // TODO: Test the loading texture code path. - // TODO: Test data-url code paths. - - it('should properly register bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charA.texture.frame.x).to.equal(2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charB.texture.frame.x).to.equal(2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charC.texture.frame.x).to.equal(23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charD.texture.frame.x).to.equal(19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register SCALED bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); - const font = BitmapText.registerFont(this.fontScaledXML, 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register bitmap font NESTED into spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); - - it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); - - spritesheet.resolution = 1; - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); -}); - -describe('PIXI.loaders.parseBitmapFontData', function () -{ - it('should exist', function () - { - expect(parseBitmapFontData).to.be.a('function'); - }); - - // TODO: Test the parser code. -}); diff --git a/packages/loaders/test/index.js b/packages/loaders/test/index.js index f00de5f..72c6554 100644 --- a/packages/loaders/test/index.js +++ b/packages/loaders/test/index.js @@ -1,4 +1,2 @@ -require('./bitmapFontParser'); require('./loader'); -require('./spritesheetParser'); require('./textureParser'); diff --git a/packages/loaders/test/resources/atlas.json b/packages/loaders/test/resources/atlas.json deleted file mode 100644 index 86e65a5..0000000 --- a/packages/loaders/test/resources/atlas.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas.png b/packages/loaders/test/resources/atlas.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/atlas@0.5x.json b/packages/loaders/test/resources/atlas@0.5x.json deleted file mode 100644 index ae990a1..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas@0.5x.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas@0.5x.png b/packages/loaders/test/resources/atlas@0.5x.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font.fnt b/packages/loaders/test/resources/font.fnt deleted file mode 100644 index 56e1060..0000000 --- a/packages/loaders/test/resources/font.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font.png b/packages/loaders/test/resources/font.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font@0.5x.fnt b/packages/loaders/test/resources/font@0.5x.fnt deleted file mode 100644 index 6c247c7..0000000 --- a/packages/loaders/test/resources/font@0.5x.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font@0.5x.png b/packages/loaders/test/resources/font@0.5x.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/spritesheetParser.js b/packages/loaders/test/spritesheetParser.js deleted file mode 100644 index 98890b8..0000000 --- a/packages/loaders/test/spritesheetParser.js +++ /dev/null @@ -1,213 +0,0 @@ -const path = require('path'); -const { spritesheetParser, Resource, Loader, getResourcePath } = require('../'); -const { Texture, BaseTexture } = require('@pixi/core'); - -describe('PIXI.loaders.spritesheetParser', function () -{ - it('should exist and return a function', function () - { - expect(spritesheetParser).to.be.a('function'); - expect(spritesheetParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not JSON', function () - { - const spy = sinon.spy(); - const res = {}; - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is JSON, but improper format', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, {}); - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should load the image & create textures if json is properly formatted', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); - const loader = new Loader(); - const addStub = sinon.stub(loader, 'add'); - const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); - - imgRes.texture = new Texture(new BaseTexture(imgRes.data)); - - addStub.yields(imgRes); - - spritesheetParser().call(loader, res, spy); - - addStub.restore(); - - expect(spy).to.have.been.calledOnce; - expect(addStub).to.have.been.calledWith( - `${res.name}_image`, - `${path.dirname(res.url)}/${res.data.meta.image}` - ); - expect(res).to.have.property('textures') - .that.is.an('object') - .with.keys(Object.keys(getJsonSpritesheet().frames)) - .and.has.property('0.png') - .that.is.an.instanceof(Texture); - }); - - it('should build the image url', function () - { - function getPath(url, image) - { - return getResourcePath({ - url, - data: { meta: { image } }, - }); - } - - let result = getPath('http://some.com/spritesheet.json', 'img.png'); - - expect(result).to.be.equals('http://some.com/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('http://some.com/some/img.png'); - - result = getPath('/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/img.png'); - - result = getPath('/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('/some/img.png'); - }); - - // TODO: Test that rectangles are created correctly. - // TODO: Test that bathc processing works correctly. - // TODO: Test that resolution processing works correctly. - // TODO: Test that metadata is honored. -}); - -function createMockResource(type, data) -{ - const name = `${Math.floor(Date.now() * Math.random())}`; - - return { - url: `http://localhost/doesnt_exist/${name}`, - name, - type, - data, - metadata: {}, - }; -} - -function getJsonSpritesheet() -{ - /* eslint-disable */ - return {"frames": { - "0.png": - { - "frame": {"x":14,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "1.png": - { - "frame": {"x":14,"y":42,"w":12,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, - "sourceSize": {"w":12,"h":14} - }, - "2.png": - { - "frame": {"x":14,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "3.png": - { - "frame": {"x":42,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "4.png": - { - "frame": {"x":28,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "5.png": - { - "frame": {"x":14,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "6.png": - { - "frame": {"x":0,"y":42,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "7.png": - { - "frame": {"x":0,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "8.png": - { - "frame": {"x":0,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "9.png": - { - "frame": {"x":0,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }}, - "meta": { - "app": "http://www.texturepacker.com", - "version": "1.0", - "image": "hud.png", - "format": "RGBA8888", - "size": {"w":64,"h":64}, - "scale": "1", - "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" - } - }; - /* eslint-enable */ -} diff --git a/packages/mixin-app-loader/LICENSE b/packages/mixin-app-loader/LICENSE new file mode 100644 index 0000000..5d16b50 --- /dev/null +++ b/packages/mixin-app-loader/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2013-2017 Mathew Groves, Chad Engler + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/packages/mixin-app-loader/README.md b/packages/mixin-app-loader/README.md new file mode 100644 index 0000000..3e89f86 --- /dev/null +++ b/packages/mixin-app-loader/README.md @@ -0,0 +1,13 @@ +# @pixi/mixin-app-loader + +## Installation + +```bash +npm install @pixi/mixin-app-loader +``` + +## Usage + +```js +import '@pixi/mixin-app-loader'; +``` \ No newline at end of file diff --git a/packages/mixin-app-loader/package.json b/packages/mixin-app-loader/package.json new file mode 100644 index 0000000..869ed4e --- /dev/null +++ b/packages/mixin-app-loader/package.json @@ -0,0 +1,41 @@ +{ + "name": "@pixi/mixin-app-loader", + "version": "5.0.0-alpha", + "main": "lib/mixin-app-loader.js", + "module": "lib/mixin-app-loader.es.js", + "description": "Support for loader in Application", + "author": "Mat Groves", + "contributors": [ + "Matt Karl " + ], + "homepage": "http://pixijs.com/", + "bugs": "https://github.com/pixijs/pixi.js/issues", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/pixijs/pixi.js.git" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "build:dev": "rollup -c", + "build": "rollup -cp", + "watch": "rollup -cw", + "postversion": "npm run build", + "test": "tester" + }, + "files": [ + "lib" + ], + "dependencies": { + "@pixi/app": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" + }, + "devDependencies": { + "@pixi/utils": "^5.0.0-alpha", + "@internal/tester": "^5.0.0-alpha", + "@internal/builder": "^5.0.0-alpha", + "rollup": "^0.50.0" + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/rollup.config.js b/packages/mixin-app-loader/rollup.config.js new file mode 100644 index 0000000..a73c874 --- /dev/null +++ b/packages/mixin-app-loader/rollup.config.js @@ -0,0 +1 @@ +export { default } from '@internal/builder'; diff --git a/packages/mixin-app-loader/src/index.js b/packages/mixin-app-loader/src/index.js new file mode 100644 index 0000000..354997a --- /dev/null +++ b/packages/mixin-app-loader/src/index.js @@ -0,0 +1,38 @@ +import { Application } from '@pixi/app'; +import { Loader, shared } from '@pixi/loaders'; + +Application.prototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperties(Application.prototype, { + loader: { + get() + { + if (!this._loader && this._options) + { + const { sharedLoader } = this._options; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +Application.prototype._parentDestroy = Application.prototype.destroy; +Application.prototype.destroy = function destroy(removeView) +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(removeView); +}; diff --git a/packages/mixin-app-loader/test/.eslintrc.json b/packages/mixin-app-loader/test/.eslintrc.json new file mode 100644 index 0000000..2094b04 --- /dev/null +++ b/packages/mixin-app-loader/test/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "globals": { + "expect": false, + "assert": false, + "sinon": false, + "PIXI": false + }, + "rules": { + "func-names": 0, + "no-unused-expressions": 0 + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/test/index.js b/packages/mixin-app-loader/test/index.js new file mode 100644 index 0000000..3468fe5 --- /dev/null +++ b/packages/mixin-app-loader/test/index.js @@ -0,0 +1,35 @@ +const { Application } = require('@pixi/app'); +const { Loader, shared } = require('@pixi/loaders'); +const { skipHello } = require('@pixi/utils'); + +require('../'); + +skipHello(); + +describe('PIXI.Application#loader', function () +{ + it('should contain loader property', function () + { + const obj = new Application(); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); + + it('should use sharedLoader option', function () + { + const obj = new Application({ sharedLoader: true }); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + expect(obj.loader).to.equal(shared); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); +}); diff --git a/packages/spritesheet/package.json b/packages/spritesheet/package.json index 34749e2..5f35442 100644 --- a/packages/spritesheet/package.json +++ b/packages/spritesheet/package.json @@ -31,7 +31,8 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/math": "^5.0.0-alpha", - "@pixi/utils": "^5.0.0-alpha" + "@pixi/utils": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" }, "devDependencies": { "@internal/builder": "^5.0.0-alpha", diff --git a/packages/spritesheet/src/SpritesheetLoader.js b/packages/spritesheet/src/SpritesheetLoader.js new file mode 100644 index 0000000..c7b34ea --- /dev/null +++ b/packages/spritesheet/src/SpritesheetLoader.js @@ -0,0 +1,83 @@ +import url from 'url'; +import { Resource, Loader } from '@pixi/loaders'; +import Spritesheet from './Spritesheet'; + +/** + * {@link PIXI.loaders.Loader Loader} middleware for loading + * texture atlases that have been created with TexturePacker or + * similar JSON-based spritesheet. This automatically generates + * Texture resources. + * @class + * @memberof PIXI + */ +export default class SpritesheetLoader +{ + /** + * Middleware function to support Spritesheets, this is automatically installed. + * @see PIXI.loaders.Loader.useMiddleware + */ + static middleware() + { + return function spritesheetLoader(resource, next) + { + const imageResourceName = `${resource.name}_image`; + + // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists + if (!resource.data + || resource.type !== Resource.TYPE.JSON + || !resource.data.frames + || this.resources[imageResourceName] + ) + { + next(); + + return; + } + + const loadOptions = { + crossOrigin: resource.crossOrigin, + loadType: Resource.LOAD_TYPE.IMAGE, + metadata: resource.metadata.imageMetadata, + parentResource: resource, + }; + + const resourcePath = SpritesheetLoader.getResourcePath(resource, this.baseUrl); + + // load the image for this sheet + this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) + { + const spritesheet = new Spritesheet( + res.texture.baseTexture, + resource.data, + resource.url + ); + + spritesheet.parse(() => + { + resource.spritesheet = spritesheet; + resource.textures = spritesheet.textures; + next(); + }); + }); + }; + } + + /** + * Get the spritesheets root path + * @param {PIXI.loader.Resource} resource - Resource to check path + * @param {string} baseUrl - Base root url + */ + static getResourcePath(resource, baseUrl) + { + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); + } +} + +// Install loader support for Spritesheet objects +Loader.useMiddleware(SpritesheetLoader.middleware); diff --git a/packages/spritesheet/src/index.js b/packages/spritesheet/src/index.js index 51d96a1..b9ee1e8 100644 --- a/packages/spritesheet/src/index.js +++ b/packages/spritesheet/src/index.js @@ -1 +1,2 @@ export { default as Spritesheet } from './Spritesheet'; +export { default as SpritesheetLoader } from './SpritesheetLoader'; diff --git a/packages/spritesheet/test/Spritesheet.js b/packages/spritesheet/test/Spritesheet.js new file mode 100644 index 0000000..35d8688 --- /dev/null +++ b/packages/spritesheet/test/Spritesheet.js @@ -0,0 +1,105 @@ +const { Spritesheet } = require('../'); +const { BaseTexture, Texture } = require('@pixi/core'); +const path = require('path'); + +describe('PIXI.Spritesheet', function () +{ + before(function () + { + this.resources = path.join(__dirname, 'resources'); + this.validate = function (spritesheet, done) + { + spritesheet.parse(function (textures) + { + const id = 'goldmine_10_5.png'; + const width = Math.floor(spritesheet.data.frames[id].frame.w); + const height = Math.floor(spritesheet.data.frames[id].frame.h); + + expect(Object.keys(textures).length).to.equal(1); + expect(Object.keys(spritesheet.textures).length).to.equal(1); + expect(textures[id]).to.be.an.instanceof(Texture); + expect(textures[id].width).to.equal(width / spritesheet.resolution); + expect(textures[id].height).to.equal(height / spritesheet.resolution); + expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); + spritesheet.destroy(true); + expect(spritesheet.textures).to.be.null; + expect(spritesheet.baseTexture).to.be.null; + done(); + }); + }; + }); + + it('should exist on PIXI', function () + { + expect(Spritesheet).to.be.a.function; + expect(Spritesheet.BATCH_SIZE).to.be.a.number; + }); + + it('should create an instance', function () + { + const baseTexture = new BaseTexture(); + const data = { + frames: {}, + meta: {}, + }; + + const spritesheet = new Spritesheet(baseTexture, data); + + expect(spritesheet.data).to.equal(data); + expect(spritesheet.baseTexture).to.equal(baseTexture); + expect(spritesheet.resolution).to.equal(1); + + spritesheet.destroy(true); + }); + + it('should create instance with scale resolution', function (done) + { + const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require + const image = new Image(); + + image.src = path.join(this.resources, data.meta.image); + image.onload = () => + { + const baseTexture = new BaseTexture(image, null, 1); + const spritesheet = new Spritesheet(baseTexture, data); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1.png'); + expect(spritesheet.resolution).to.equal(0.5); + this.validate(spritesheet, done); + }; + }); + + it('should create instance with BaseTexture source scale', function (done) + { + const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require + const baseTexture = new BaseTexture.fromImage(data.meta.image, undefined, undefined, 1.5); + const spritesheet = new Spritesheet(baseTexture, data); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1.png'); + expect(spritesheet.resolution).to.equal(0.5); + + this.validate(spritesheet, done); + }); + + it('should create instance with filename resolution', function (done) + { + const uri = path.resolve(this.resources, 'building1@2x.json'); + const data = require(uri); // eslint-disable-line global-require + const image = new Image(); + + image.src = path.join(this.resources, data.meta.image); + image.onload = () => + { + const baseTexture = new BaseTexture(image, null, 1); + const spritesheet = new Spritesheet(baseTexture, data, uri); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1@2x.png'); + expect(spritesheet.resolution).to.equal(2); + + this.validate(spritesheet, done); + }; + }); +}); diff --git a/packages/spritesheet/test/SpritesheetLoader.js b/packages/spritesheet/test/SpritesheetLoader.js new file mode 100644 index 0000000..e4205ec --- /dev/null +++ b/packages/spritesheet/test/SpritesheetLoader.js @@ -0,0 +1,214 @@ +const path = require('path'); +const { Loader, Resource } = require('@pixi/loaders'); +const { Texture, BaseTexture } = require('@pixi/core'); +const { SpritesheetLoader } = require('../'); + +describe('PIXI.SpritesheetLoader', function () +{ + it('should exist and return a function', function () + { + expect(SpritesheetLoader.middleware).to.be.a('function'); + expect(SpritesheetLoader.middleware()).to.be.a('function'); + }); + + it('should do nothing if the resource is not JSON', function () + { + const spy = sinon.spy(); + const res = {}; + + SpritesheetLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should do nothing if the resource is JSON, but improper format', function () + { + const spy = sinon.spy(); + const res = createMockResource(Resource.TYPE.JSON, {}); + + SpritesheetLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should load the image & create textures if json is properly formatted', function () + { + const spy = sinon.spy(); + const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); + const loader = new Loader(); + const addStub = sinon.stub(loader, 'add'); + const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); + + imgRes.texture = new Texture(new BaseTexture(imgRes.data)); + + addStub.yields(imgRes); + + SpritesheetLoader.middleware().call(loader, res, spy); + + addStub.restore(); + + expect(spy).to.have.been.calledOnce; + expect(addStub).to.have.been.calledWith( + `${res.name}_image`, + `${path.dirname(res.url)}/${res.data.meta.image}` + ); + expect(res).to.have.property('textures') + .that.is.an('object') + .with.keys(Object.keys(getJsonSpritesheet().frames)) + .and.has.property('0.png') + .that.is.an.instanceof(Texture); + }); + + it('should build the image url', function () + { + function getPath(url, image) + { + return SpritesheetLoader.getResourcePath({ + url, + data: { meta: { image } }, + }); + } + + let result = getPath('http://some.com/spritesheet.json', 'img.png'); + + expect(result).to.be.equals('http://some.com/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); + expect(result).to.be.equals('http://some.com/some/dir/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); + expect(result).to.be.equals('http://some.com/some/dir/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); + expect(result).to.be.equals('http://some.com/some/img.png'); + + result = getPath('/spritesheet.json', 'img.png'); + expect(result).to.be.equals('/img.png'); + + result = getPath('/some/dir/spritesheet.json', 'img.png'); + expect(result).to.be.equals('/some/dir/img.png'); + + result = getPath('/some/dir/spritesheet.json', './img.png'); + expect(result).to.be.equals('/some/dir/img.png'); + + result = getPath('/some/dir/spritesheet.json', '../img.png'); + expect(result).to.be.equals('/some/img.png'); + }); + + // TODO: Test that rectangles are created correctly. + // TODO: Test that bathc processing works correctly. + // TODO: Test that resolution processing works correctly. + // TODO: Test that metadata is honored. +}); + +function createMockResource(type, data) +{ + const name = `${Math.floor(Date.now() * Math.random())}`; + + return { + url: `http://localhost/doesnt_exist/${name}`, + name, + type, + data, + metadata: {}, + }; +} + +function getJsonSpritesheet() +{ + /* eslint-disable */ + return {"frames": { + "0.png": + { + "frame": {"x":14,"y":28,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "1.png": + { + "frame": {"x":14,"y":42,"w":12,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, + "sourceSize": {"w":12,"h":14} + }, + "2.png": + { + "frame": {"x":14,"y":14,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "3.png": + { + "frame": {"x":42,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "4.png": + { + "frame": {"x":28,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "5.png": + { + "frame": {"x":14,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "6.png": + { + "frame": {"x":0,"y":42,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "7.png": + { + "frame": {"x":0,"y":28,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "8.png": + { + "frame": {"x":0,"y":14,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "9.png": + { + "frame": {"x":0,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }}, + "meta": { + "app": "http://www.texturepacker.com", + "version": "1.0", + "image": "hud.png", + "format": "RGBA8888", + "size": {"w":64,"h":64}, + "scale": "1", + "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" + } + }; + /* eslint-enable */ +} diff --git a/packages/spritesheet/test/index.js b/packages/spritesheet/test/index.js index 35d8688..8cf5636 100644 --- a/packages/spritesheet/test/index.js +++ b/packages/spritesheet/test/index.js @@ -1,105 +1,2 @@ -const { Spritesheet } = require('../'); -const { BaseTexture, Texture } = require('@pixi/core'); -const path = require('path'); - -describe('PIXI.Spritesheet', function () -{ - before(function () - { - this.resources = path.join(__dirname, 'resources'); - this.validate = function (spritesheet, done) - { - spritesheet.parse(function (textures) - { - const id = 'goldmine_10_5.png'; - const width = Math.floor(spritesheet.data.frames[id].frame.w); - const height = Math.floor(spritesheet.data.frames[id].frame.h); - - expect(Object.keys(textures).length).to.equal(1); - expect(Object.keys(spritesheet.textures).length).to.equal(1); - expect(textures[id]).to.be.an.instanceof(Texture); - expect(textures[id].width).to.equal(width / spritesheet.resolution); - expect(textures[id].height).to.equal(height / spritesheet.resolution); - expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); - spritesheet.destroy(true); - expect(spritesheet.textures).to.be.null; - expect(spritesheet.baseTexture).to.be.null; - done(); - }); - }; - }); - - it('should exist on PIXI', function () - { - expect(Spritesheet).to.be.a.function; - expect(Spritesheet.BATCH_SIZE).to.be.a.number; - }); - - it('should create an instance', function () - { - const baseTexture = new BaseTexture(); - const data = { - frames: {}, - meta: {}, - }; - - const spritesheet = new Spritesheet(baseTexture, data); - - expect(spritesheet.data).to.equal(data); - expect(spritesheet.baseTexture).to.equal(baseTexture); - expect(spritesheet.resolution).to.equal(1); - - spritesheet.destroy(true); - }); - - it('should create instance with scale resolution', function (done) - { - const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require - const image = new Image(); - - image.src = path.join(this.resources, data.meta.image); - image.onload = () => - { - const baseTexture = new BaseTexture(image, null, 1); - const spritesheet = new Spritesheet(baseTexture, data); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1.png'); - expect(spritesheet.resolution).to.equal(0.5); - this.validate(spritesheet, done); - }; - }); - - it('should create instance with BaseTexture source scale', function (done) - { - const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require - const baseTexture = new BaseTexture.fromImage(data.meta.image, undefined, undefined, 1.5); - const spritesheet = new Spritesheet(baseTexture, data); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1.png'); - expect(spritesheet.resolution).to.equal(0.5); - - this.validate(spritesheet, done); - }); - - it('should create instance with filename resolution', function (done) - { - const uri = path.resolve(this.resources, 'building1@2x.json'); - const data = require(uri); // eslint-disable-line global-require - const image = new Image(); - - image.src = path.join(this.resources, data.meta.image); - image.onload = () => - { - const baseTexture = new BaseTexture(image, null, 1); - const spritesheet = new Spritesheet(baseTexture, data, uri); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1@2x.png'); - expect(spritesheet.resolution).to.equal(2); - - this.validate(spritesheet, done); - }; - }); -}); +require('./Spritesheet'); +require('./SpritesheetLoader'); diff --git a/packages/text-bitmap/package.json b/packages/text-bitmap/package.json index a8a5f95..4b47da6 100644 --- a/packages/text-bitmap/package.json +++ b/packages/text-bitmap/package.json @@ -23,7 +23,7 @@ "build": "rollup -cp", "watch": "rollup -cw", "postversion": "npm run build", - "test": "exit 0" + "test": "tester" }, "files": [ "lib" @@ -31,12 +31,15 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha", "@pixi/sprite": "^5.0.0-alpha", "@pixi/settings": "^5.0.0-alpha", "@pixi/math": "^5.0.0-alpha", "@pixi/display": "^5.0.0-alpha" }, "devDependencies": { + "@pixi/spritesheet": "^5.0.0-alpha", + "@internal/tester": "^5.0.0-alpha", "@internal/builder": "^5.0.0-alpha", "rollup": "^0.50.0" } diff --git a/packages/text-bitmap/src/BitmapFontLoader.js b/packages/text-bitmap/src/BitmapFontLoader.js new file mode 100644 index 0000000..3956060 --- /dev/null +++ b/packages/text-bitmap/src/BitmapFontLoader.js @@ -0,0 +1,112 @@ +import * as path from 'path'; +import { TextureCache } from '@pixi/utils'; +import { Resource, Loader } from '@pixi/loaders'; +import BitmapText from './BitmapText'; + +/** + * {@link PIXI.loaders.Loader Loader} middleware for loading + * bitmap-based fonts suitable for using with {@link PIXI.BitmapText}. + * @class + * @memberof PIXI + */ +export default class BitmapFontLoader +{ + /** + * Register a BitmapText font from loader resource. + * + * @param {PIXI.loaders.Resource} resource - Loader resource. + * @param {PIXI.Texture} texture - Reference to texture. + */ + static parse(resource, texture) + { + resource.bitmapFont = BitmapText.registerFont(resource.data, texture); + } + + /** + * Middleware function to support BitmapText fonts, this is automatically installed. + * @see PIXI.loaders.Loader.useMiddleware + */ + static middleware() + { + return function bitmapFontLoader(resource, next) + { + // skip if no data or not xml data + if (!resource.data || resource.type !== Resource.TYPE.XML) + { + next(); + + return; + } + + // skip if not bitmap font data, using some silly duck-typing + if (resource.data.getElementsByTagName('page').length === 0 + || resource.data.getElementsByTagName('info').length === 0 + || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null + ) + { + next(); + + return; + } + + let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; + + if (resource.isDataUrl) + { + if (xmlUrl === '.') + { + xmlUrl = ''; + } + + if (this.baseUrl && xmlUrl) + { + // if baseurl has a trailing slash then add one to xmlUrl so the replace works below + if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') + { + xmlUrl += '/'; + } + } + } + + // remove baseUrl from xmlUrl + xmlUrl = xmlUrl.replace(this.baseUrl, ''); + + // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. + if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') + { + xmlUrl += '/'; + } + + const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); + + if (TextureCache[textureUrl]) + { + // reuse existing texture + BitmapFontLoader.parse(resource, TextureCache[textureUrl]); + next(); + } + else + { + const loadOptions = { + crossOrigin: resource.crossOrigin, + loadType: Resource.LOAD_TYPE.IMAGE, + metadata: resource.metadata.imageMetadata, + parentResource: resource, + }; + + // load the texture for the font + this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => + { + BitmapFontLoader.parse(resource, res.texture); + next(); + }); + } + }; + } +} + +// Add custom add support for loading fnt files as XML +Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); + +// Install loader support for BitmapText +Loader.useMiddleware(BitmapFontLoader.middleware); diff --git a/packages/text-bitmap/src/index.js b/packages/text-bitmap/src/index.js index a98e7b4..e5afd2c 100644 --- a/packages/text-bitmap/src/index.js +++ b/packages/text-bitmap/src/index.js @@ -1 +1,2 @@ export { default as BitmapText } from './BitmapText'; +export { default as BitmapFontLoader } from './BitmapFontLoader'; diff --git a/packages/text-bitmap/test/.eslintrc.json b/packages/text-bitmap/test/.eslintrc.json new file mode 100644 index 0000000..2094b04 --- /dev/null +++ b/packages/text-bitmap/test/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "globals": { + "expect": false, + "assert": false, + "sinon": false, + "PIXI": false + }, + "rules": { + "func-names": 0, + "no-unused-expressions": 0 + } +} \ No newline at end of file diff --git a/packages/text-bitmap/test/BitmapFontLoader.js b/packages/text-bitmap/test/BitmapFontLoader.js new file mode 100644 index 0000000..a76fc1c --- /dev/null +++ b/packages/text-bitmap/test/BitmapFontLoader.js @@ -0,0 +1,319 @@ +const path = require('path'); +const fs = require('fs'); +const { BaseTextureCache, TextureCache } = require('@pixi/utils'); +const { Texture, BaseTexture } = require('@pixi/core'); +const { Spritesheet } = require('@pixi/spritesheet'); +const { BitmapText, BitmapFontLoader } = require('../'); + +describe('PIXI.BitmapFontLoader', function () +{ + afterEach(function () + { + for (const font in BitmapText.fonts) + { + delete BitmapText.fonts[font]; + } + for (const baseTexture in BaseTextureCache) + { + delete BaseTextureCache[baseTexture]; + } + for (const texture in TextureCache) + { + delete TextureCache[texture]; + } + }); + + before(function (done) + { + const resolveURL = (url) => path.resolve(this.resources, url); + + this.resources = path.join(__dirname, 'resources'); + this.fontXML = null; + this.fontScaledXML = null; + this.fontImage = null; + this.fontScaledImage = null; + this.atlasImage = null; + this.atlasScaledImage = null; + this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require + this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require + + const loadXML = (url) => new Promise((resolve) => + fs.readFile(resolveURL(url), 'utf8', (err, data) => + { + expect(err).to.be.null; + resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); + })); + + const loadImage = (url) => new Promise((resolve) => + { + const image = new Image(); + + image.onload = () => resolve(image); + image.src = resolveURL(url); + }); + + Promise.all([ + loadXML('font.fnt'), + loadXML('font@0.5x.fnt'), + loadImage('font.png'), + loadImage('font@0.5x.png'), + loadImage('atlas.png'), + loadImage('atlas@0.5x.png'), + ]).then(([ + fontXML, + fontScaledXML, + fontImage, + fontScaledImage, + atlasImage, + atlasScaledImage, + ]) => + { + this.fontXML = fontXML; + this.fontScaledXML = fontScaledXML; + this.fontImage = fontImage; + this.fontScaledImage = fontScaledImage; + this.atlasImage = atlasImage; + this.atlasScaledImage = atlasScaledImage; + done(); + }); + }); + + it('should exist and return a function', function () + { + expect(BitmapFontLoader.middleware).to.be.a('function'); + expect(BitmapFontLoader.middleware()).to.be.a('function'); + }); + + it('should do nothing if the resource is not XML', function () + { + const spy = sinon.spy(); + const res = {}; + + BitmapFontLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should do nothing if the resource is not properly formatted XML', function () + { + const spy = sinon.spy(); + const res = { data: document.createDocumentFragment() }; + + BitmapFontLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + // TODO: Test the texture cache code path. + // TODO: Test the loading texture code path. + // TODO: Test data-url code paths. + + it('should properly register bitmap font', function (done) + { + const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); + 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charA.texture.frame.x).to.equal(2); + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charB.texture.frame.x).to.equal(2); + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charC.texture.frame.x).to.equal(23); + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charD.texture.frame.x).to.equal(19); + 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]; + + expect(charE).to.be.undefined; + done(); + }); + + it('should properly register SCALED bitmap font', function (done) + { + const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); + const font = BitmapText.registerFont(this.fontScaledXML, 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 + 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]; + + expect(charE).to.be.undefined; + done(); + }); + + it('should properly register bitmap font NESTED into spritesheet', function (done) + { + const baseTexture = new BaseTexture(this.atlasImage, null, 1); + const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); + + spritesheet.parse(() => + { + const fontTexture = Texture.fromFrame('resources/font.png'); + const font = BitmapText.registerFont(this.fontXML, fontTexture); + const fontX = 158; // bare value from spritesheet frame + const fontY = 2; // bare value from spritesheet frame + + 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charA.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charB.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charC.texture.frame.x).to.equal(fontX + 23); + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charD.texture.frame.x).to.equal(fontX + 19); + 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]; + + expect(charE).to.be.undefined; + done(); + }); + }); + + it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) + { + const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); + const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); + + spritesheet.resolution = 1; + + spritesheet.parse(() => + { + const fontTexture = Texture.fromFrame('resources/font.png'); + const font = BitmapText.registerFont(this.fontXML, fontTexture); + const fontX = 158; // bare value from spritesheet frame + const fontY = 2; // bare value from spritesheet frame + + 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charA.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charB.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charC.texture.frame.x).to.equal(fontX + 23); + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charD.texture.frame.x).to.equal(fontX + 19); + 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]; + + expect(charE).to.be.undefined; + done(); + }); + }); + + it('should parse exist', function () + { + expect(BitmapFontLoader.parse).to.be.a('function'); + }); +}); diff --git a/packages/text-bitmap/test/index.js b/packages/text-bitmap/test/index.js new file mode 100644 index 0000000..688fee2 --- /dev/null +++ b/packages/text-bitmap/test/index.js @@ -0,0 +1 @@ +require('./BitmapFontLoader'); diff --git a/packages/text-bitmap/test/resources/atlas.json b/packages/text-bitmap/test/resources/atlas.json new file mode 100644 index 0000000..86e65a5 --- /dev/null +++ b/packages/text-bitmap/test/resources/atlas.json @@ -0,0 +1,25 @@ +{ + "meta": { + "image": "atlas.png", + "size": {"w":256,"h":256}, + "scale": "1" + }, + "frames": { + "resources/test.png": + { + "frame": {"x":2,"y":2,"w":152,"h":188}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, + "sourceSize": {"w":300,"h":225} + }, + "resources/font.png": + { + "frame": {"x":158,"y":2,"w":40,"h":42}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, + "sourceSize": {"w":43,"h":46} + } + } +} \ No newline at end of file diff --git a/packages/text-bitmap/test/resources/atlas.png b/packages/text-bitmap/test/resources/atlas.png new file mode 100644 index 0000000..d5e7892 --- /dev/null +++ b/packages/text-bitmap/test/resources/atlas.png Binary files differ diff --git a/packages/text-bitmap/test/resources/atlas@0.5x.json b/packages/text-bitmap/test/resources/atlas@0.5x.json new file mode 100644 index 0000000..ae990a1 --- /dev/null +++ b/packages/text-bitmap/test/resources/atlas@0.5x.json @@ -0,0 +1,25 @@ +{ + "meta": { + "image": "atlas@0.5x.png", + "size": {"w":256,"h":256}, + "scale": "1" + }, + "frames": { + "resources/test.png": + { + "frame": {"x":2,"y":2,"w":152,"h":188}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, + "sourceSize": {"w":300,"h":225} + }, + "resources/font.png": + { + "frame": {"x":158,"y":2,"w":40,"h":42}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, + "sourceSize": {"w":43,"h":46} + } + } +} \ No newline at end of file diff --git a/packages/text-bitmap/test/resources/atlas@0.5x.png b/packages/text-bitmap/test/resources/atlas@0.5x.png new file mode 100644 index 0000000..d5e7892 --- /dev/null +++ b/packages/text-bitmap/test/resources/atlas@0.5x.png Binary files differ diff --git a/packages/text-bitmap/test/resources/font.fnt b/packages/text-bitmap/test/resources/font.fnt new file mode 100644 index 0000000..56e1060 --- /dev/null +++ b/packages/text-bitmap/test/resources/font.fnt @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/text-bitmap/test/resources/font.png b/packages/text-bitmap/test/resources/font.png new file mode 100644 index 0000000..cf772e9 --- /dev/null +++ b/packages/text-bitmap/test/resources/font.png Binary files differ diff --git a/packages/text-bitmap/test/resources/font@0.5x.fnt b/packages/text-bitmap/test/resources/font@0.5x.fnt new file mode 100644 index 0000000..6c247c7 --- /dev/null +++ b/packages/text-bitmap/test/resources/font@0.5x.fnt @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/bundles/pixi.js/package.json b/bundles/pixi.js/package.json index 54dcdad..db88209 100644 --- a/bundles/pixi.js/package.json +++ b/bundles/pixi.js/package.json @@ -52,6 +52,7 @@ "@pixi/mixin-cache-as-bitmap": "^5.0.0-alpha", "@pixi/mixin-get-child-by-name": "^5.0.0-alpha", "@pixi/mixin-get-global-position": "^5.0.0-alpha", + "@pixi/mixin-app-loader": "^5.0.0-alpha", "@pixi/particles": "^5.0.0-alpha", "@pixi/polyfill": "^5.0.0-alpha", "@pixi/prepare": "^5.0.0-alpha", diff --git a/bundles/pixi.js/src/deprecated.js b/bundles/pixi.js/src/deprecated.js index 214817f..a529402 100644 --- a/bundles/pixi.js/src/deprecated.js +++ b/bundles/pixi.js/src/deprecated.js @@ -153,3 +153,74 @@ }, }, }); + +Object.defineProperties(PIXI.loaders.Loader, { + /** + * @function PIXI.loaders.Loader.addPixiMiddleware + * @see PIXI.loaders.Loader.useMiddleware + * @deprecated since 5.0.0 + */ + addPixiMiddleware: { + get() + { + warn('PIXI.loaders.Loader.addPixiMiddleware has moved to PIXI.loaders.Loader.useMiddleware'); + + return PIXI.loaders.Loader.useMiddleware; + }, + }, +}); + +Object.defineProperties(PIXI.loaders, { + /** + * @function PIXI.loaders.bitmapFontParser + * @see PIXI.BitmapFontLoader.middleware + * @deprecated since 5.0.0 + */ + bitmapFontParser: { + get() + { + warn('PIXI.loaders.bitmapFontParser has moved to PIXI.BitmapFontLoader.middleware'); + + return PIXI.BitmapFontLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.parseBitmapFontData + * @see PIXI.BitmapFontLoader.parse + * @deprecated since 5.0.0 + */ + parseBitmapFontData: { + get() + { + warn('PIXI.loaders.parseBitmapFontData has moved to PIXI.BitmapFontLoader.parse'); + + return PIXI.BitmapFontLoader.parse; + }, + }, + /** + * @function PIXI.loaders.spritesheetParser + * @see PIXI.SpritesheetLoader.middleware + * @deprecated since 5.0.0 + */ + spritesheetParser: { + get() + { + warn('PIXI.loaders.spritesheetParser has moved to PIXI.SpritesheetLoader.middleware'); + + return PIXI.SpritesheetLoader.middleware; + }, + }, + /** + * @function PIXI.loaders.getResourcePath + * @see PIXI.SpritesheetLoader.getResourcePath + * @deprecated since 5.0.0 + */ + getResourcePath: { + get() + { + warn('PIXI.loaders.getResourcePath has moved to PIXI.SpritesheetLoader.getResourcePath'); + + return PIXI.SpritesheetLoader.getResourcePath; + }, + }, +}); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 56b17fe..2a9082c 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -32,6 +32,7 @@ import '@pixi/mixin-cache-as-bitmap'; import '@pixi/mixin-get-child-by-name'; import '@pixi/mixin-get-global-position'; +import '@pixi/mixin-app-loader'; // handle mixins now, after all code has been added utils.mixins.performMixins(); diff --git a/packages/loaders/package.json b/packages/loaders/package.json index 4e95914..f0cba09 100644 --- a/packages/loaders/package.json +++ b/packages/loaders/package.json @@ -31,9 +31,6 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", - "@pixi/app": "^5.0.0-alpha", - "@pixi/spritesheet": "^5.0.0-alpha", - "@pixi/text-bitmap": "^5.0.0-alpha", "resource-loader": "^2.0.9", "eventemitter3": "^2.0.0" }, diff --git a/packages/loaders/src/Loader.js b/packages/loaders/src/Loader.js new file mode 100644 index 0000000..6f6e9bd --- /dev/null +++ b/packages/loaders/src/Loader.js @@ -0,0 +1,144 @@ +import ResourceLoader from 'resource-loader'; +import EventEmitter from 'eventemitter3'; +import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; +import textureParser from './textureParser'; + +/** + * + * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader + * + * ```js + * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. + * //or + * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want + * + * const sprites = {}; + * + * // Chainable `add` to enqueue a resource + * loader.add('bunny', 'data/bunny.png') + * .add('spaceship', 'assets/spritesheet.json'); + * loader.add('scoreFont', 'assets/score.fnt'); + * + * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. + * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). + * loader.pre(cachingMiddleware); + * + * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. + * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). + * loader.use(parsingMiddleware); + * + * // The `load` method loads the queue of resources, and calls the passed in callback called once all + * // resources have loaded. + * loader.load((loader, resources) => { + * // resources is an object where the key is the name of the resource loaded and the value is the resource object. + * // They have a couple default properties: + * // - `url`: The URL that the resource was loaded from + * // - `error`: The error that happened when trying to load (if any) + * // - `data`: The raw data that was loaded + * // also may contain other properties based on the middleware that runs. + * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); + * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); + * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); + * }); + * + * // throughout the process multiple signals can be dispatched. + * loader.onProgress.add(() => {}); // called once per loaded/errored file + * loader.onError.add(() => {}); // called once per errored file + * loader.onLoad.add(() => {}); // called once per loaded file + * loader.onComplete.add(() => {}); // called once when the queued resources all load. + * ``` + * + * @see https://github.com/englercj/resource-loader + * + * @class Loader + * @extends module:resource-loader.ResourceLoader + * @memberof PIXI.loaders + * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. + * @param {number} [concurrency=10] - The number of resources to load concurrently. + */ +export class Loader extends ResourceLoader +{ + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); + EventEmitter.call(this); + + for (let i = 0; i < Loader._middleware.length; ++i) + { + this.use(Loader._middleware[i]()); + } + + // Compat layer, translate the new v2 signals into old v1 events. + this.onStart.add((l) => this.emit('start', l)); + this.onProgress.add((l, r) => this.emit('progress', l, r)); + this.onError.add((e, l, r) => this.emit('error', e, l, r)); + this.onLoad.add((l, r) => this.emit('load', l, r)); + this.onComplete.add((l, r) => this.emit('complete', l, r)); + + /** + * If this loader cannot be destroyed. + * @member {boolean} + * @default false + * @private + */ + this._protected = false; + } + + /** + * Destroy the loader, removes references. + */ + destroy() + { + if (!this._protected) + { + this.removeAllListeners(); + this.reset(); + } + } +} + +// Copy EE3 prototype (mixin) +Object.assign(Loader.prototype, EventEmitter.prototype); + +/** + * Collection of all installed middleware for Loader. + * + * @static + * @member {Array} + * @memberof PIXI.loaders.Loader + * @private + */ +Loader._middleware = []; + +/** + * A premade instance of the loader that can be used to load resources. + * @name shared + * @memberof PIXI.loaders + * @type {PIXI.loaders.Loader} + */ +export const shared = new Loader(); +shared._protected = true; + +/** + * Adds a middleware for the global shared loader and all + * new Loader instances created. + * + * @static + * @method useMiddleware + * @memberof PIXI.loaders.Loader + * @param {Function} fn - The middleware to add. + */ +Loader.useMiddleware = function useMiddleware(fn) +{ + // Install for current shared loader + shared.use(fn); + + // Install for all future loaders + Loader._middleware.push(fn); +}; + +// parse any blob into more usable objects (e.g. Image) +Loader.useMiddleware(blobMiddlewareFactory); + +// parse any Image objects into textures +Loader.useMiddleware(textureParser); diff --git a/packages/loaders/src/bitmapFontParser.js b/packages/loaders/src/bitmapFontParser.js deleted file mode 100644 index 1e4fa1d..0000000 --- a/packages/loaders/src/bitmapFontParser.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as path from 'path'; -import { TextureCache } from '@pixi/utils'; -import { Resource } from 'resource-loader'; -import { BitmapText } from '@pixi/text-bitmap'; - -/** - * Register a BitmapText font from loader resource. - * - * @function parseBitmapFontData - * @memberof PIXI.loaders - * @param {PIXI.loaders.Resource} resource - Loader resource. - * @param {PIXI.Texture} texture - Reference to texture. - */ -export function parse(resource, texture) -{ - resource.bitmapFont = BitmapText.registerFont(resource.data, texture); -} - -export default function () -{ - return function bitmapFontParser(resource, next) - { - // skip if no data or not xml data - if (!resource.data || resource.type !== Resource.TYPE.XML) - { - next(); - - return; - } - - // skip if not bitmap font data, using some silly duck-typing - if (resource.data.getElementsByTagName('page').length === 0 - || resource.data.getElementsByTagName('info').length === 0 - || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null - ) - { - next(); - - return; - } - - let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; - - if (resource.isDataUrl) - { - if (xmlUrl === '.') - { - xmlUrl = ''; - } - - if (this.baseUrl && xmlUrl) - { - // if baseurl has a trailing slash then add one to xmlUrl so the replace works below - if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') - { - xmlUrl += '/'; - } - } - } - - // remove baseUrl from xmlUrl - xmlUrl = xmlUrl.replace(this.baseUrl, ''); - - // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. - if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') - { - xmlUrl += '/'; - } - - const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); - - if (TextureCache[textureUrl]) - { - // reuse existing texture - parse(resource, TextureCache[textureUrl]); - next(); - } - else - { - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - // load the texture for the font - this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => - { - parse(resource, res.texture); - next(); - }); - } - }; -} diff --git a/packages/loaders/src/index.js b/packages/loaders/src/index.js index cff0c9e..8e816cc 100644 --- a/packages/loaders/src/index.js +++ b/packages/loaders/src/index.js @@ -1,5 +1,11 @@ -import { Application } from '@pixi/app'; -import Loader from './loader'; +/** + * Reference to **{@link https://github.com/englercj/resource-loader + * resource-loader}**'s Resource class. + * @see http://englercj.github.io/resource-loader/Resource.html + * @class Resource + * @memberof PIXI.loaders + */ +export { Resource } from 'resource-loader'; /** * This namespace contains APIs which extends the {@link https://github.com/englercj/resource-loader resource-loader} module @@ -14,67 +20,5 @@ * }); * @namespace PIXI.loaders */ -export { Loader }; -export { default as bitmapFontParser, parse as parseBitmapFontData } from './bitmapFontParser'; -export { default as spritesheetParser, getResourcePath } from './spritesheetParser'; +export { Loader, shared } from './Loader'; export { default as textureParser } from './textureParser'; - -/** - * Reference to **resource-loader**'s Resource class. - * See https://github.com/englercj/resource-loader - * @class Resource - * @memberof PIXI.loaders - */ -export { Resource } from 'resource-loader'; - -/** - * A premade instance of the loader that can be used to load resources. - * @name shared - * @memberof PIXI.loaders - * @type {PIXI.loaders.Loader} - */ -const shared = new Loader(); - -shared.destroy = () => -{ - // protect destroying shared loader -}; - -export { shared }; - -// Mixin the loader construction -const AppPrototype = Application.prototype; - -AppPrototype._loader = null; - -/** - * Loader instance to help with asset loading. - * @name PIXI.Application#loader - * @type {PIXI.loaders.Loader} - */ -Object.defineProperty(AppPrototype, 'loader', { - get() - { - if (!this._loader) - { - const sharedLoader = this._options.sharedLoader; - - this._loader = sharedLoader ? shared : new Loader(); - } - - return this._loader; - }, -}); - -// Override the destroy function -// making sure to destroy the current Loader -AppPrototype._parentDestroy = AppPrototype.destroy; -AppPrototype.destroy = function destroy(removeView) -{ - if (this._loader) - { - this._loader.destroy(); - this._loader = null; - } - this._parentDestroy(removeView); -}; diff --git a/packages/loaders/src/loader.js b/packages/loaders/src/loader.js deleted file mode 100644 index 93356a3..0000000 --- a/packages/loaders/src/loader.js +++ /dev/null @@ -1,124 +0,0 @@ -import ResourceLoader from 'resource-loader'; -import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob'; -import EventEmitter from 'eventemitter3'; -import textureParser from './textureParser'; -import spritesheetParser from './spritesheetParser'; -import bitmapFontParser from './bitmapFontParser'; - -/** - * - * The new loader, extends Resource Loader by Chad Engler: https://github.com/englercj/resource-loader - * - * ```js - * const loader = PIXI.loader; // PixiJS exposes a premade instance for you to use. - * //or - * const loader = new PIXI.loaders.Loader(); // you can also create your own if you want - * - * const sprites = {}; - * - * // Chainable `add` to enqueue a resource - * loader.add('bunny', 'data/bunny.png') - * .add('spaceship', 'assets/spritesheet.json'); - * loader.add('scoreFont', 'assets/score.fnt'); - * - * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource. - * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc). - * loader.pre(cachingMiddleware); - * - * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource. - * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc). - * loader.use(parsingMiddleware); - * - * // The `load` method loads the queue of resources, and calls the passed in callback called once all - * // resources have loaded. - * loader.load((loader, resources) => { - * // resources is an object where the key is the name of the resource loaded and the value is the resource object. - * // They have a couple default properties: - * // - `url`: The URL that the resource was loaded from - * // - `error`: The error that happened when trying to load (if any) - * // - `data`: The raw data that was loaded - * // also may contain other properties based on the middleware that runs. - * sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture); - * sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture); - * sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture); - * }); - * - * // throughout the process multiple signals can be dispatched. - * loader.onProgress.add(() => {}); // called once per loaded/errored file - * loader.onError.add(() => {}); // called once per errored file - * loader.onLoad.add(() => {}); // called once per loaded file - * loader.onComplete.add(() => {}); // called once when the queued resources all load. - * ``` - * - * @see https://github.com/englercj/resource-loader - * - * @class - * @extends module:resource-loader.ResourceLoader - * @memberof PIXI.loaders - */ -export default class Loader extends ResourceLoader -{ - /** - * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader. - * @param {number} [concurrency=10] - The number of resources to load concurrently. - */ - constructor(baseUrl, concurrency) - { - super(baseUrl, concurrency); - EventEmitter.call(this); - - for (let i = 0; i < Loader._pixiMiddleware.length; ++i) - { - this.use(Loader._pixiMiddleware[i]()); - } - - // Compat layer, translate the new v2 signals into old v1 events. - this.onStart.add((l) => this.emit('start', l)); - this.onProgress.add((l, r) => this.emit('progress', l, r)); - this.onError.add((e, l, r) => this.emit('error', e, l, r)); - this.onLoad.add((l, r) => this.emit('load', l, r)); - this.onComplete.add((l, r) => this.emit('complete', l, r)); - } - - /** - * Adds a default middleware to the PixiJS loader. - * - * @static - * @param {Function} fn - The middleware to add. - */ - static addPixiMiddleware(fn) - { - Loader._pixiMiddleware.push(fn); - } - - /** - * Destroy the loader, removes references. - */ - destroy() - { - this.removeAllListeners(); - this.reset(); - } -} - -// Copy EE3 prototype (mixin) -for (const k in EventEmitter.prototype) -{ - Loader.prototype[k] = EventEmitter.prototype[k]; -} - -Loader._pixiMiddleware = [ - // parse any blob into more usable objects (e.g. Image) - blobMiddlewareFactory, - // parse any Image objects into textures - textureParser, - // parse any spritesheet data into multiple textures - spritesheetParser, - // parse bitmap font data into multiple textures - bitmapFontParser, -]; - -// Add custom extentions -const Resource = ResourceLoader.Resource; - -Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); diff --git a/packages/loaders/src/spritesheetParser.js b/packages/loaders/src/spritesheetParser.js deleted file mode 100644 index f7fa206..0000000 --- a/packages/loaders/src/spritesheetParser.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Resource } from 'resource-loader'; -import url from 'url'; -import { Spritesheet } from '@pixi/spritesheet'; - -export default function () -{ - return function spritesheetParser(resource, next) - { - const imageResourceName = `${resource.name}_image`; - - // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists - if (!resource.data - || resource.type !== Resource.TYPE.JSON - || !resource.data.frames - || this.resources[imageResourceName] - ) - { - next(); - - return; - } - - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - - const resourcePath = getResourcePath(resource, this.baseUrl); - - // load the image for this sheet - this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) - { - const spritesheet = new Spritesheet( - res.texture.baseTexture, - resource.data, - resource.url - ); - - spritesheet.parse(() => - { - resource.spritesheet = spritesheet; - resource.textures = spritesheet.textures; - next(); - }); - }); - }; -} - -export function getResourcePath(resource, baseUrl) -{ - // Prepend url path unless the resource image is a data url - if (resource.isDataUrl) - { - return resource.data.meta.image; - } - - return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); -} diff --git a/packages/loaders/test/bitmapFontParser.js b/packages/loaders/test/bitmapFontParser.js deleted file mode 100644 index 43ef194..0000000 --- a/packages/loaders/test/bitmapFontParser.js +++ /dev/null @@ -1,325 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const { BitmapText } = require('@pixi/text-bitmap'); -const { BaseTextureCache, TextureCache } = require('@pixi/utils'); -const { Texture, BaseTexture } = require('@pixi/core'); -const { Spritesheet } = require('@pixi/spritesheet'); -const { bitmapFontParser, parseBitmapFontData } = require('../'); - -describe('PIXI.loaders.bitmapFontParser', function () -{ - afterEach(function () - { - for (const font in BitmapText.fonts) - { - delete BitmapText.fonts[font]; - } - for (const baseTexture in BaseTextureCache) - { - delete BaseTextureCache[baseTexture]; - } - for (const texture in TextureCache) - { - delete TextureCache[texture]; - } - }); - - before(function (done) - { - const resolveURL = (url) => path.resolve(this.resources, url); - - this.resources = path.join(__dirname, 'resources'); - this.fontXML = null; - this.fontScaledXML = null; - this.fontImage = null; - this.fontScaledImage = null; - this.atlasImage = null; - this.atlasScaledImage = null; - this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require - this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require - - const loadXML = (url) => new Promise((resolve) => - fs.readFile(resolveURL(url), 'utf8', (err, data) => - { - expect(err).to.be.null; - resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); - })); - - const loadImage = (url) => new Promise((resolve) => - { - const image = new Image(); - - image.onload = () => resolve(image); - image.src = resolveURL(url); - }); - - Promise.all([ - loadXML('font.fnt'), - loadXML('font@0.5x.fnt'), - loadImage('font.png'), - loadImage('font@0.5x.png'), - loadImage('atlas.png'), - loadImage('atlas@0.5x.png'), - ]).then(([ - fontXML, - fontScaledXML, - fontImage, - fontScaledImage, - atlasImage, - atlasScaledImage, - ]) => - { - this.fontXML = fontXML; - this.fontScaledXML = fontScaledXML; - this.fontImage = fontImage; - this.fontScaledImage = fontScaledImage; - this.atlasImage = atlasImage; - this.atlasScaledImage = atlasScaledImage; - done(); - }); - }); - - it('should exist and return a function', function () - { - expect(bitmapFontParser).to.be.a('function'); - expect(bitmapFontParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not XML', function () - { - const spy = sinon.spy(); - const res = {}; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is not properly formatted XML', function () - { - const spy = sinon.spy(); - const res = { data: document.createDocumentFragment() }; - - bitmapFontParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - // TODO: Test the texture cache code path. - // TODO: Test the loading texture code path. - // TODO: Test data-url code paths. - - it('should properly register bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charA.texture.frame.x).to.equal(2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charB.texture.frame.x).to.equal(2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charC.texture.frame.x).to.equal(23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); - expect(charD.texture.frame.x).to.equal(19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register SCALED bitmap font', function (done) - { - const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); - const font = BitmapText.registerFont(this.fontScaledXML, 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); - expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 - 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]; - - expect(charE).to.be.undefined; - done(); - }); - - it('should properly register bitmap font NESTED into spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); - - it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) - { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); - const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); - - spritesheet.resolution = 1; - - spritesheet.parse(() => - { - const fontTexture = Texture.fromFrame('resources/font.png'); - const font = BitmapText.registerFont(this.fontXML, fontTexture); - const fontX = 158; // bare value from spritesheet frame - const fontY = 2; // bare value from spritesheet frame - - 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]; - - expect(charA).to.exist; - expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charA.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charB).to.exist; - expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charB.texture.frame.x).to.equal(fontX + 2); - 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]; - - expect(charC).to.exist; - expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charC.texture.frame.x).to.equal(fontX + 23); - 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]; - - expect(charD).to.exist; - expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); - expect(charD.texture.frame.x).to.equal(fontX + 19); - 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]; - - expect(charE).to.be.undefined; - done(); - }); - }); -}); - -describe('PIXI.loaders.parseBitmapFontData', function () -{ - it('should exist', function () - { - expect(parseBitmapFontData).to.be.a('function'); - }); - - // TODO: Test the parser code. -}); diff --git a/packages/loaders/test/index.js b/packages/loaders/test/index.js index f00de5f..72c6554 100644 --- a/packages/loaders/test/index.js +++ b/packages/loaders/test/index.js @@ -1,4 +1,2 @@ -require('./bitmapFontParser'); require('./loader'); -require('./spritesheetParser'); require('./textureParser'); diff --git a/packages/loaders/test/resources/atlas.json b/packages/loaders/test/resources/atlas.json deleted file mode 100644 index 86e65a5..0000000 --- a/packages/loaders/test/resources/atlas.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas.png b/packages/loaders/test/resources/atlas.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/atlas@0.5x.json b/packages/loaders/test/resources/atlas@0.5x.json deleted file mode 100644 index ae990a1..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "meta": { - "image": "atlas@0.5x.png", - "size": {"w":256,"h":256}, - "scale": "1" - }, - "frames": { - "resources/test.png": - { - "frame": {"x":2,"y":2,"w":152,"h":188}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, - "sourceSize": {"w":300,"h":225} - }, - "resources/font.png": - { - "frame": {"x":158,"y":2,"w":40,"h":42}, - "rotated": false, - "trimmed": true, - "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, - "sourceSize": {"w":43,"h":46} - } - } -} \ No newline at end of file diff --git a/packages/loaders/test/resources/atlas@0.5x.png b/packages/loaders/test/resources/atlas@0.5x.png deleted file mode 100644 index d5e7892..0000000 --- a/packages/loaders/test/resources/atlas@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font.fnt b/packages/loaders/test/resources/font.fnt deleted file mode 100644 index 56e1060..0000000 --- a/packages/loaders/test/resources/font.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font.png b/packages/loaders/test/resources/font.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/resources/font@0.5x.fnt b/packages/loaders/test/resources/font@0.5x.fnt deleted file mode 100644 index 6c247c7..0000000 --- a/packages/loaders/test/resources/font@0.5x.fnt +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/loaders/test/resources/font@0.5x.png b/packages/loaders/test/resources/font@0.5x.png deleted file mode 100644 index cf772e9..0000000 --- a/packages/loaders/test/resources/font@0.5x.png +++ /dev/null Binary files differ diff --git a/packages/loaders/test/spritesheetParser.js b/packages/loaders/test/spritesheetParser.js deleted file mode 100644 index 98890b8..0000000 --- a/packages/loaders/test/spritesheetParser.js +++ /dev/null @@ -1,213 +0,0 @@ -const path = require('path'); -const { spritesheetParser, Resource, Loader, getResourcePath } = require('../'); -const { Texture, BaseTexture } = require('@pixi/core'); - -describe('PIXI.loaders.spritesheetParser', function () -{ - it('should exist and return a function', function () - { - expect(spritesheetParser).to.be.a('function'); - expect(spritesheetParser()).to.be.a('function'); - }); - - it('should do nothing if the resource is not JSON', function () - { - const spy = sinon.spy(); - const res = {}; - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should do nothing if the resource is JSON, but improper format', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, {}); - - spritesheetParser()(res, spy); - - expect(spy).to.have.been.calledOnce; - expect(res.textures).to.be.undefined; - }); - - it('should load the image & create textures if json is properly formatted', function () - { - const spy = sinon.spy(); - const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); - const loader = new Loader(); - const addStub = sinon.stub(loader, 'add'); - const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); - - imgRes.texture = new Texture(new BaseTexture(imgRes.data)); - - addStub.yields(imgRes); - - spritesheetParser().call(loader, res, spy); - - addStub.restore(); - - expect(spy).to.have.been.calledOnce; - expect(addStub).to.have.been.calledWith( - `${res.name}_image`, - `${path.dirname(res.url)}/${res.data.meta.image}` - ); - expect(res).to.have.property('textures') - .that.is.an('object') - .with.keys(Object.keys(getJsonSpritesheet().frames)) - .and.has.property('0.png') - .that.is.an.instanceof(Texture); - }); - - it('should build the image url', function () - { - function getPath(url, image) - { - return getResourcePath({ - url, - data: { meta: { image } }, - }); - } - - let result = getPath('http://some.com/spritesheet.json', 'img.png'); - - expect(result).to.be.equals('http://some.com/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('http://some.com/some/dir/img.png'); - - result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('http://some.com/some/img.png'); - - result = getPath('/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/img.png'); - - result = getPath('/some/dir/spritesheet.json', 'img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', './img.png'); - expect(result).to.be.equals('/some/dir/img.png'); - - result = getPath('/some/dir/spritesheet.json', '../img.png'); - expect(result).to.be.equals('/some/img.png'); - }); - - // TODO: Test that rectangles are created correctly. - // TODO: Test that bathc processing works correctly. - // TODO: Test that resolution processing works correctly. - // TODO: Test that metadata is honored. -}); - -function createMockResource(type, data) -{ - const name = `${Math.floor(Date.now() * Math.random())}`; - - return { - url: `http://localhost/doesnt_exist/${name}`, - name, - type, - data, - metadata: {}, - }; -} - -function getJsonSpritesheet() -{ - /* eslint-disable */ - return {"frames": { - "0.png": - { - "frame": {"x":14,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "1.png": - { - "frame": {"x":14,"y":42,"w":12,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, - "sourceSize": {"w":12,"h":14} - }, - "2.png": - { - "frame": {"x":14,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "3.png": - { - "frame": {"x":42,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "4.png": - { - "frame": {"x":28,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "5.png": - { - "frame": {"x":14,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "6.png": - { - "frame": {"x":0,"y":42,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "7.png": - { - "frame": {"x":0,"y":28,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "8.png": - { - "frame": {"x":0,"y":14,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }, - "9.png": - { - "frame": {"x":0,"y":0,"w":14,"h":14}, - "rotated": false, - "trimmed": false, - "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} - }}, - "meta": { - "app": "http://www.texturepacker.com", - "version": "1.0", - "image": "hud.png", - "format": "RGBA8888", - "size": {"w":64,"h":64}, - "scale": "1", - "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" - } - }; - /* eslint-enable */ -} diff --git a/packages/mixin-app-loader/LICENSE b/packages/mixin-app-loader/LICENSE new file mode 100644 index 0000000..5d16b50 --- /dev/null +++ b/packages/mixin-app-loader/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2013-2017 Mathew Groves, Chad Engler + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/packages/mixin-app-loader/README.md b/packages/mixin-app-loader/README.md new file mode 100644 index 0000000..3e89f86 --- /dev/null +++ b/packages/mixin-app-loader/README.md @@ -0,0 +1,13 @@ +# @pixi/mixin-app-loader + +## Installation + +```bash +npm install @pixi/mixin-app-loader +``` + +## Usage + +```js +import '@pixi/mixin-app-loader'; +``` \ No newline at end of file diff --git a/packages/mixin-app-loader/package.json b/packages/mixin-app-loader/package.json new file mode 100644 index 0000000..869ed4e --- /dev/null +++ b/packages/mixin-app-loader/package.json @@ -0,0 +1,41 @@ +{ + "name": "@pixi/mixin-app-loader", + "version": "5.0.0-alpha", + "main": "lib/mixin-app-loader.js", + "module": "lib/mixin-app-loader.es.js", + "description": "Support for loader in Application", + "author": "Mat Groves", + "contributors": [ + "Matt Karl " + ], + "homepage": "http://pixijs.com/", + "bugs": "https://github.com/pixijs/pixi.js/issues", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/pixijs/pixi.js.git" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "build:dev": "rollup -c", + "build": "rollup -cp", + "watch": "rollup -cw", + "postversion": "npm run build", + "test": "tester" + }, + "files": [ + "lib" + ], + "dependencies": { + "@pixi/app": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" + }, + "devDependencies": { + "@pixi/utils": "^5.0.0-alpha", + "@internal/tester": "^5.0.0-alpha", + "@internal/builder": "^5.0.0-alpha", + "rollup": "^0.50.0" + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/rollup.config.js b/packages/mixin-app-loader/rollup.config.js new file mode 100644 index 0000000..a73c874 --- /dev/null +++ b/packages/mixin-app-loader/rollup.config.js @@ -0,0 +1 @@ +export { default } from '@internal/builder'; diff --git a/packages/mixin-app-loader/src/index.js b/packages/mixin-app-loader/src/index.js new file mode 100644 index 0000000..354997a --- /dev/null +++ b/packages/mixin-app-loader/src/index.js @@ -0,0 +1,38 @@ +import { Application } from '@pixi/app'; +import { Loader, shared } from '@pixi/loaders'; + +Application.prototype._loader = null; + +/** + * Loader instance to help with asset loading. + * @name PIXI.Application#loader + * @type {PIXI.loaders.Loader} + */ +Object.defineProperties(Application.prototype, { + loader: { + get() + { + if (!this._loader && this._options) + { + const { sharedLoader } = this._options; + + this._loader = sharedLoader ? shared : new Loader(); + } + + return this._loader; + }, + }, +}); + +// Override the destroy function +// making sure to destroy the current Loader +Application.prototype._parentDestroy = Application.prototype.destroy; +Application.prototype.destroy = function destroy(removeView) +{ + if (this._loader) + { + this._loader.destroy(); + this._loader = null; + } + this._parentDestroy(removeView); +}; diff --git a/packages/mixin-app-loader/test/.eslintrc.json b/packages/mixin-app-loader/test/.eslintrc.json new file mode 100644 index 0000000..2094b04 --- /dev/null +++ b/packages/mixin-app-loader/test/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "globals": { + "expect": false, + "assert": false, + "sinon": false, + "PIXI": false + }, + "rules": { + "func-names": 0, + "no-unused-expressions": 0 + } +} \ No newline at end of file diff --git a/packages/mixin-app-loader/test/index.js b/packages/mixin-app-loader/test/index.js new file mode 100644 index 0000000..3468fe5 --- /dev/null +++ b/packages/mixin-app-loader/test/index.js @@ -0,0 +1,35 @@ +const { Application } = require('@pixi/app'); +const { Loader, shared } = require('@pixi/loaders'); +const { skipHello } = require('@pixi/utils'); + +require('../'); + +skipHello(); + +describe('PIXI.Application#loader', function () +{ + it('should contain loader property', function () + { + const obj = new Application(); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); + + it('should use sharedLoader option', function () + { + const obj = new Application({ sharedLoader: true }); + + expect(obj.loader).to.be.not.undefined; + expect(obj.loader).to.be.instanceof(Loader); + expect(obj.loader).to.equal(shared); + + obj.destroy(); + + expect(obj.loader).to.be.null; + }); +}); diff --git a/packages/spritesheet/package.json b/packages/spritesheet/package.json index 34749e2..5f35442 100644 --- a/packages/spritesheet/package.json +++ b/packages/spritesheet/package.json @@ -31,7 +31,8 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/math": "^5.0.0-alpha", - "@pixi/utils": "^5.0.0-alpha" + "@pixi/utils": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha" }, "devDependencies": { "@internal/builder": "^5.0.0-alpha", diff --git a/packages/spritesheet/src/SpritesheetLoader.js b/packages/spritesheet/src/SpritesheetLoader.js new file mode 100644 index 0000000..c7b34ea --- /dev/null +++ b/packages/spritesheet/src/SpritesheetLoader.js @@ -0,0 +1,83 @@ +import url from 'url'; +import { Resource, Loader } from '@pixi/loaders'; +import Spritesheet from './Spritesheet'; + +/** + * {@link PIXI.loaders.Loader Loader} middleware for loading + * texture atlases that have been created with TexturePacker or + * similar JSON-based spritesheet. This automatically generates + * Texture resources. + * @class + * @memberof PIXI + */ +export default class SpritesheetLoader +{ + /** + * Middleware function to support Spritesheets, this is automatically installed. + * @see PIXI.loaders.Loader.useMiddleware + */ + static middleware() + { + return function spritesheetLoader(resource, next) + { + const imageResourceName = `${resource.name}_image`; + + // skip if no data, its not json, it isn't spritesheet data, or the image resource already exists + if (!resource.data + || resource.type !== Resource.TYPE.JSON + || !resource.data.frames + || this.resources[imageResourceName] + ) + { + next(); + + return; + } + + const loadOptions = { + crossOrigin: resource.crossOrigin, + loadType: Resource.LOAD_TYPE.IMAGE, + metadata: resource.metadata.imageMetadata, + parentResource: resource, + }; + + const resourcePath = SpritesheetLoader.getResourcePath(resource, this.baseUrl); + + // load the image for this sheet + this.add(imageResourceName, resourcePath, loadOptions, function onImageLoad(res) + { + const spritesheet = new Spritesheet( + res.texture.baseTexture, + resource.data, + resource.url + ); + + spritesheet.parse(() => + { + resource.spritesheet = spritesheet; + resource.textures = spritesheet.textures; + next(); + }); + }); + }; + } + + /** + * Get the spritesheets root path + * @param {PIXI.loader.Resource} resource - Resource to check path + * @param {string} baseUrl - Base root url + */ + static getResourcePath(resource, baseUrl) + { + // Prepend url path unless the resource image is a data url + if (resource.isDataUrl) + { + return resource.data.meta.image; + } + + return url.resolve(resource.url.replace(baseUrl, ''), resource.data.meta.image); + } +} + +// Install loader support for Spritesheet objects +Loader.useMiddleware(SpritesheetLoader.middleware); diff --git a/packages/spritesheet/src/index.js b/packages/spritesheet/src/index.js index 51d96a1..b9ee1e8 100644 --- a/packages/spritesheet/src/index.js +++ b/packages/spritesheet/src/index.js @@ -1 +1,2 @@ export { default as Spritesheet } from './Spritesheet'; +export { default as SpritesheetLoader } from './SpritesheetLoader'; diff --git a/packages/spritesheet/test/Spritesheet.js b/packages/spritesheet/test/Spritesheet.js new file mode 100644 index 0000000..35d8688 --- /dev/null +++ b/packages/spritesheet/test/Spritesheet.js @@ -0,0 +1,105 @@ +const { Spritesheet } = require('../'); +const { BaseTexture, Texture } = require('@pixi/core'); +const path = require('path'); + +describe('PIXI.Spritesheet', function () +{ + before(function () + { + this.resources = path.join(__dirname, 'resources'); + this.validate = function (spritesheet, done) + { + spritesheet.parse(function (textures) + { + const id = 'goldmine_10_5.png'; + const width = Math.floor(spritesheet.data.frames[id].frame.w); + const height = Math.floor(spritesheet.data.frames[id].frame.h); + + expect(Object.keys(textures).length).to.equal(1); + expect(Object.keys(spritesheet.textures).length).to.equal(1); + expect(textures[id]).to.be.an.instanceof(Texture); + expect(textures[id].width).to.equal(width / spritesheet.resolution); + expect(textures[id].height).to.equal(height / spritesheet.resolution); + expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); + spritesheet.destroy(true); + expect(spritesheet.textures).to.be.null; + expect(spritesheet.baseTexture).to.be.null; + done(); + }); + }; + }); + + it('should exist on PIXI', function () + { + expect(Spritesheet).to.be.a.function; + expect(Spritesheet.BATCH_SIZE).to.be.a.number; + }); + + it('should create an instance', function () + { + const baseTexture = new BaseTexture(); + const data = { + frames: {}, + meta: {}, + }; + + const spritesheet = new Spritesheet(baseTexture, data); + + expect(spritesheet.data).to.equal(data); + expect(spritesheet.baseTexture).to.equal(baseTexture); + expect(spritesheet.resolution).to.equal(1); + + spritesheet.destroy(true); + }); + + it('should create instance with scale resolution', function (done) + { + const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require + const image = new Image(); + + image.src = path.join(this.resources, data.meta.image); + image.onload = () => + { + const baseTexture = new BaseTexture(image, null, 1); + const spritesheet = new Spritesheet(baseTexture, data); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1.png'); + expect(spritesheet.resolution).to.equal(0.5); + this.validate(spritesheet, done); + }; + }); + + it('should create instance with BaseTexture source scale', function (done) + { + const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require + const baseTexture = new BaseTexture.fromImage(data.meta.image, undefined, undefined, 1.5); + const spritesheet = new Spritesheet(baseTexture, data); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1.png'); + expect(spritesheet.resolution).to.equal(0.5); + + this.validate(spritesheet, done); + }); + + it('should create instance with filename resolution', function (done) + { + const uri = path.resolve(this.resources, 'building1@2x.json'); + const data = require(uri); // eslint-disable-line global-require + const image = new Image(); + + image.src = path.join(this.resources, data.meta.image); + image.onload = () => + { + const baseTexture = new BaseTexture(image, null, 1); + const spritesheet = new Spritesheet(baseTexture, data, uri); + + expect(data).to.be.an.object; + expect(data.meta.image).to.equal('building1@2x.png'); + expect(spritesheet.resolution).to.equal(2); + + this.validate(spritesheet, done); + }; + }); +}); diff --git a/packages/spritesheet/test/SpritesheetLoader.js b/packages/spritesheet/test/SpritesheetLoader.js new file mode 100644 index 0000000..e4205ec --- /dev/null +++ b/packages/spritesheet/test/SpritesheetLoader.js @@ -0,0 +1,214 @@ +const path = require('path'); +const { Loader, Resource } = require('@pixi/loaders'); +const { Texture, BaseTexture } = require('@pixi/core'); +const { SpritesheetLoader } = require('../'); + +describe('PIXI.SpritesheetLoader', function () +{ + it('should exist and return a function', function () + { + expect(SpritesheetLoader.middleware).to.be.a('function'); + expect(SpritesheetLoader.middleware()).to.be.a('function'); + }); + + it('should do nothing if the resource is not JSON', function () + { + const spy = sinon.spy(); + const res = {}; + + SpritesheetLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should do nothing if the resource is JSON, but improper format', function () + { + const spy = sinon.spy(); + const res = createMockResource(Resource.TYPE.JSON, {}); + + SpritesheetLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should load the image & create textures if json is properly formatted', function () + { + const spy = sinon.spy(); + const res = createMockResource(Resource.TYPE.JSON, getJsonSpritesheet()); + const loader = new Loader(); + const addStub = sinon.stub(loader, 'add'); + const imgRes = createMockResource(Resource.TYPE.IMAGE, new Image()); + + imgRes.texture = new Texture(new BaseTexture(imgRes.data)); + + addStub.yields(imgRes); + + SpritesheetLoader.middleware().call(loader, res, spy); + + addStub.restore(); + + expect(spy).to.have.been.calledOnce; + expect(addStub).to.have.been.calledWith( + `${res.name}_image`, + `${path.dirname(res.url)}/${res.data.meta.image}` + ); + expect(res).to.have.property('textures') + .that.is.an('object') + .with.keys(Object.keys(getJsonSpritesheet().frames)) + .and.has.property('0.png') + .that.is.an.instanceof(Texture); + }); + + it('should build the image url', function () + { + function getPath(url, image) + { + return SpritesheetLoader.getResourcePath({ + url, + data: { meta: { image } }, + }); + } + + let result = getPath('http://some.com/spritesheet.json', 'img.png'); + + expect(result).to.be.equals('http://some.com/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', 'img.png'); + expect(result).to.be.equals('http://some.com/some/dir/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', './img.png'); + expect(result).to.be.equals('http://some.com/some/dir/img.png'); + + result = getPath('http://some.com/some/dir/spritesheet.json', '../img.png'); + expect(result).to.be.equals('http://some.com/some/img.png'); + + result = getPath('/spritesheet.json', 'img.png'); + expect(result).to.be.equals('/img.png'); + + result = getPath('/some/dir/spritesheet.json', 'img.png'); + expect(result).to.be.equals('/some/dir/img.png'); + + result = getPath('/some/dir/spritesheet.json', './img.png'); + expect(result).to.be.equals('/some/dir/img.png'); + + result = getPath('/some/dir/spritesheet.json', '../img.png'); + expect(result).to.be.equals('/some/img.png'); + }); + + // TODO: Test that rectangles are created correctly. + // TODO: Test that bathc processing works correctly. + // TODO: Test that resolution processing works correctly. + // TODO: Test that metadata is honored. +}); + +function createMockResource(type, data) +{ + const name = `${Math.floor(Date.now() * Math.random())}`; + + return { + url: `http://localhost/doesnt_exist/${name}`, + name, + type, + data, + metadata: {}, + }; +} + +function getJsonSpritesheet() +{ + /* eslint-disable */ + return {"frames": { + "0.png": + { + "frame": {"x":14,"y":28,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "1.png": + { + "frame": {"x":14,"y":42,"w":12,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":14}, + "sourceSize": {"w":12,"h":14} + }, + "2.png": + { + "frame": {"x":14,"y":14,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "3.png": + { + "frame": {"x":42,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "4.png": + { + "frame": {"x":28,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "5.png": + { + "frame": {"x":14,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "6.png": + { + "frame": {"x":0,"y":42,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "7.png": + { + "frame": {"x":0,"y":28,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "8.png": + { + "frame": {"x":0,"y":14,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }, + "9.png": + { + "frame": {"x":0,"y":0,"w":14,"h":14}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, + "sourceSize": {"w":14,"h":14} + }}, + "meta": { + "app": "http://www.texturepacker.com", + "version": "1.0", + "image": "hud.png", + "format": "RGBA8888", + "size": {"w":64,"h":64}, + "scale": "1", + "smartupdate": "$TexturePacker:SmartUpdate:47025c98c8b10634b75172d4ed7e7edc$" + } + }; + /* eslint-enable */ +} diff --git a/packages/spritesheet/test/index.js b/packages/spritesheet/test/index.js index 35d8688..8cf5636 100644 --- a/packages/spritesheet/test/index.js +++ b/packages/spritesheet/test/index.js @@ -1,105 +1,2 @@ -const { Spritesheet } = require('../'); -const { BaseTexture, Texture } = require('@pixi/core'); -const path = require('path'); - -describe('PIXI.Spritesheet', function () -{ - before(function () - { - this.resources = path.join(__dirname, 'resources'); - this.validate = function (spritesheet, done) - { - spritesheet.parse(function (textures) - { - const id = 'goldmine_10_5.png'; - const width = Math.floor(spritesheet.data.frames[id].frame.w); - const height = Math.floor(spritesheet.data.frames[id].frame.h); - - expect(Object.keys(textures).length).to.equal(1); - expect(Object.keys(spritesheet.textures).length).to.equal(1); - expect(textures[id]).to.be.an.instanceof(Texture); - expect(textures[id].width).to.equal(width / spritesheet.resolution); - expect(textures[id].height).to.equal(height / spritesheet.resolution); - expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); - spritesheet.destroy(true); - expect(spritesheet.textures).to.be.null; - expect(spritesheet.baseTexture).to.be.null; - done(); - }); - }; - }); - - it('should exist on PIXI', function () - { - expect(Spritesheet).to.be.a.function; - expect(Spritesheet.BATCH_SIZE).to.be.a.number; - }); - - it('should create an instance', function () - { - const baseTexture = new BaseTexture(); - const data = { - frames: {}, - meta: {}, - }; - - const spritesheet = new Spritesheet(baseTexture, data); - - expect(spritesheet.data).to.equal(data); - expect(spritesheet.baseTexture).to.equal(baseTexture); - expect(spritesheet.resolution).to.equal(1); - - spritesheet.destroy(true); - }); - - it('should create instance with scale resolution', function (done) - { - const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require - const image = new Image(); - - image.src = path.join(this.resources, data.meta.image); - image.onload = () => - { - const baseTexture = new BaseTexture(image, null, 1); - const spritesheet = new Spritesheet(baseTexture, data); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1.png'); - expect(spritesheet.resolution).to.equal(0.5); - this.validate(spritesheet, done); - }; - }); - - it('should create instance with BaseTexture source scale', function (done) - { - const data = require(path.resolve(this.resources, 'building1.json')); // eslint-disable-line global-require - const baseTexture = new BaseTexture.fromImage(data.meta.image, undefined, undefined, 1.5); - const spritesheet = new Spritesheet(baseTexture, data); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1.png'); - expect(spritesheet.resolution).to.equal(0.5); - - this.validate(spritesheet, done); - }); - - it('should create instance with filename resolution', function (done) - { - const uri = path.resolve(this.resources, 'building1@2x.json'); - const data = require(uri); // eslint-disable-line global-require - const image = new Image(); - - image.src = path.join(this.resources, data.meta.image); - image.onload = () => - { - const baseTexture = new BaseTexture(image, null, 1); - const spritesheet = new Spritesheet(baseTexture, data, uri); - - expect(data).to.be.an.object; - expect(data.meta.image).to.equal('building1@2x.png'); - expect(spritesheet.resolution).to.equal(2); - - this.validate(spritesheet, done); - }; - }); -}); +require('./Spritesheet'); +require('./SpritesheetLoader'); diff --git a/packages/text-bitmap/package.json b/packages/text-bitmap/package.json index a8a5f95..4b47da6 100644 --- a/packages/text-bitmap/package.json +++ b/packages/text-bitmap/package.json @@ -23,7 +23,7 @@ "build": "rollup -cp", "watch": "rollup -cw", "postversion": "npm run build", - "test": "exit 0" + "test": "tester" }, "files": [ "lib" @@ -31,12 +31,15 @@ "dependencies": { "@pixi/core": "^5.0.0-alpha", "@pixi/utils": "^5.0.0-alpha", + "@pixi/loaders": "^5.0.0-alpha", "@pixi/sprite": "^5.0.0-alpha", "@pixi/settings": "^5.0.0-alpha", "@pixi/math": "^5.0.0-alpha", "@pixi/display": "^5.0.0-alpha" }, "devDependencies": { + "@pixi/spritesheet": "^5.0.0-alpha", + "@internal/tester": "^5.0.0-alpha", "@internal/builder": "^5.0.0-alpha", "rollup": "^0.50.0" } diff --git a/packages/text-bitmap/src/BitmapFontLoader.js b/packages/text-bitmap/src/BitmapFontLoader.js new file mode 100644 index 0000000..3956060 --- /dev/null +++ b/packages/text-bitmap/src/BitmapFontLoader.js @@ -0,0 +1,112 @@ +import * as path from 'path'; +import { TextureCache } from '@pixi/utils'; +import { Resource, Loader } from '@pixi/loaders'; +import BitmapText from './BitmapText'; + +/** + * {@link PIXI.loaders.Loader Loader} middleware for loading + * bitmap-based fonts suitable for using with {@link PIXI.BitmapText}. + * @class + * @memberof PIXI + */ +export default class BitmapFontLoader +{ + /** + * Register a BitmapText font from loader resource. + * + * @param {PIXI.loaders.Resource} resource - Loader resource. + * @param {PIXI.Texture} texture - Reference to texture. + */ + static parse(resource, texture) + { + resource.bitmapFont = BitmapText.registerFont(resource.data, texture); + } + + /** + * Middleware function to support BitmapText fonts, this is automatically installed. + * @see PIXI.loaders.Loader.useMiddleware + */ + static middleware() + { + return function bitmapFontLoader(resource, next) + { + // skip if no data or not xml data + if (!resource.data || resource.type !== Resource.TYPE.XML) + { + next(); + + return; + } + + // skip if not bitmap font data, using some silly duck-typing + if (resource.data.getElementsByTagName('page').length === 0 + || resource.data.getElementsByTagName('info').length === 0 + || resource.data.getElementsByTagName('info')[0].getAttribute('face') === null + ) + { + next(); + + return; + } + + let xmlUrl = !resource.isDataUrl ? path.dirname(resource.url) : ''; + + if (resource.isDataUrl) + { + if (xmlUrl === '.') + { + xmlUrl = ''; + } + + if (this.baseUrl && xmlUrl) + { + // if baseurl has a trailing slash then add one to xmlUrl so the replace works below + if (this.baseUrl.charAt(this.baseUrl.length - 1) === '/') + { + xmlUrl += '/'; + } + } + } + + // remove baseUrl from xmlUrl + xmlUrl = xmlUrl.replace(this.baseUrl, ''); + + // if there is an xmlUrl now, it needs a trailing slash. Ensure that it does if the string isn't empty. + if (xmlUrl && xmlUrl.charAt(xmlUrl.length - 1) !== '/') + { + xmlUrl += '/'; + } + + const textureUrl = xmlUrl + resource.data.getElementsByTagName('page')[0].getAttribute('file'); + + if (TextureCache[textureUrl]) + { + // reuse existing texture + BitmapFontLoader.parse(resource, TextureCache[textureUrl]); + next(); + } + else + { + const loadOptions = { + crossOrigin: resource.crossOrigin, + loadType: Resource.LOAD_TYPE.IMAGE, + metadata: resource.metadata.imageMetadata, + parentResource: resource, + }; + + // load the texture for the font + this.add(`${resource.name}_image`, textureUrl, loadOptions, (res) => + { + BitmapFontLoader.parse(resource, res.texture); + next(); + }); + } + }; + } +} + +// Add custom add support for loading fnt files as XML +Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT); + +// Install loader support for BitmapText +Loader.useMiddleware(BitmapFontLoader.middleware); diff --git a/packages/text-bitmap/src/index.js b/packages/text-bitmap/src/index.js index a98e7b4..e5afd2c 100644 --- a/packages/text-bitmap/src/index.js +++ b/packages/text-bitmap/src/index.js @@ -1 +1,2 @@ export { default as BitmapText } from './BitmapText'; +export { default as BitmapFontLoader } from './BitmapFontLoader'; diff --git a/packages/text-bitmap/test/.eslintrc.json b/packages/text-bitmap/test/.eslintrc.json new file mode 100644 index 0000000..2094b04 --- /dev/null +++ b/packages/text-bitmap/test/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "globals": { + "expect": false, + "assert": false, + "sinon": false, + "PIXI": false + }, + "rules": { + "func-names": 0, + "no-unused-expressions": 0 + } +} \ No newline at end of file diff --git a/packages/text-bitmap/test/BitmapFontLoader.js b/packages/text-bitmap/test/BitmapFontLoader.js new file mode 100644 index 0000000..a76fc1c --- /dev/null +++ b/packages/text-bitmap/test/BitmapFontLoader.js @@ -0,0 +1,319 @@ +const path = require('path'); +const fs = require('fs'); +const { BaseTextureCache, TextureCache } = require('@pixi/utils'); +const { Texture, BaseTexture } = require('@pixi/core'); +const { Spritesheet } = require('@pixi/spritesheet'); +const { BitmapText, BitmapFontLoader } = require('../'); + +describe('PIXI.BitmapFontLoader', function () +{ + afterEach(function () + { + for (const font in BitmapText.fonts) + { + delete BitmapText.fonts[font]; + } + for (const baseTexture in BaseTextureCache) + { + delete BaseTextureCache[baseTexture]; + } + for (const texture in TextureCache) + { + delete TextureCache[texture]; + } + }); + + before(function (done) + { + const resolveURL = (url) => path.resolve(this.resources, url); + + this.resources = path.join(__dirname, 'resources'); + this.fontXML = null; + this.fontScaledXML = null; + this.fontImage = null; + this.fontScaledImage = null; + this.atlasImage = null; + this.atlasScaledImage = null; + this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require + this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require + + const loadXML = (url) => new Promise((resolve) => + fs.readFile(resolveURL(url), 'utf8', (err, data) => + { + expect(err).to.be.null; + resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); + })); + + const loadImage = (url) => new Promise((resolve) => + { + const image = new Image(); + + image.onload = () => resolve(image); + image.src = resolveURL(url); + }); + + Promise.all([ + loadXML('font.fnt'), + loadXML('font@0.5x.fnt'), + loadImage('font.png'), + loadImage('font@0.5x.png'), + loadImage('atlas.png'), + loadImage('atlas@0.5x.png'), + ]).then(([ + fontXML, + fontScaledXML, + fontImage, + fontScaledImage, + atlasImage, + atlasScaledImage, + ]) => + { + this.fontXML = fontXML; + this.fontScaledXML = fontScaledXML; + this.fontImage = fontImage; + this.fontScaledImage = fontScaledImage; + this.atlasImage = atlasImage; + this.atlasScaledImage = atlasScaledImage; + done(); + }); + }); + + it('should exist and return a function', function () + { + expect(BitmapFontLoader.middleware).to.be.a('function'); + expect(BitmapFontLoader.middleware()).to.be.a('function'); + }); + + it('should do nothing if the resource is not XML', function () + { + const spy = sinon.spy(); + const res = {}; + + BitmapFontLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + it('should do nothing if the resource is not properly formatted XML', function () + { + const spy = sinon.spy(); + const res = { data: document.createDocumentFragment() }; + + BitmapFontLoader.middleware()(res, spy); + + expect(spy).to.have.been.calledOnce; + expect(res.textures).to.be.undefined; + }); + + // TODO: Test the texture cache code path. + // TODO: Test the loading texture code path. + // TODO: Test data-url code paths. + + it('should properly register bitmap font', function (done) + { + const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); + 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charA.texture.frame.x).to.equal(2); + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charB.texture.frame.x).to.equal(2); + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charC.texture.frame.x).to.equal(23); + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.fontImage); + expect(charD.texture.frame.x).to.equal(19); + 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]; + + expect(charE).to.be.undefined; + done(); + }); + + it('should properly register SCALED bitmap font', function (done) + { + const texture = new Texture(new BaseTexture(this.fontScaledImage, null, 0.5)); + const font = BitmapText.registerFont(this.fontScaledXML, 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.fontScaledImage); + expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 + 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]; + + expect(charE).to.be.undefined; + done(); + }); + + it('should properly register bitmap font NESTED into spritesheet', function (done) + { + const baseTexture = new BaseTexture(this.atlasImage, null, 1); + const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); + + spritesheet.parse(() => + { + const fontTexture = Texture.fromFrame('resources/font.png'); + const font = BitmapText.registerFont(this.fontXML, fontTexture); + const fontX = 158; // bare value from spritesheet frame + const fontY = 2; // bare value from spritesheet frame + + 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charA.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charB.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charC.texture.frame.x).to.equal(fontX + 23); + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasImage); + expect(charD.texture.frame.x).to.equal(fontX + 19); + 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]; + + expect(charE).to.be.undefined; + done(); + }); + }); + + it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) + { + const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); + const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); + + spritesheet.resolution = 1; + + spritesheet.parse(() => + { + const fontTexture = Texture.fromFrame('resources/font.png'); + const font = BitmapText.registerFont(this.fontXML, fontTexture); + const fontX = 158; // bare value from spritesheet frame + const fontY = 2; // bare value from spritesheet frame + + 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]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charA.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charB.texture.frame.x).to.equal(fontX + 2); + 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]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charC.texture.frame.x).to.equal(fontX + 23); + 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]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.resource.source).to.equal(this.atlasScaledImage); + expect(charD.texture.frame.x).to.equal(fontX + 19); + 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]; + + expect(charE).to.be.undefined; + done(); + }); + }); + + it('should parse exist', function () + { + expect(BitmapFontLoader.parse).to.be.a('function'); + }); +}); diff --git a/packages/text-bitmap/test/index.js b/packages/text-bitmap/test/index.js new file mode 100644 index 0000000..688fee2 --- /dev/null +++ b/packages/text-bitmap/test/index.js @@ -0,0 +1 @@ +require('./BitmapFontLoader'); diff --git a/packages/text-bitmap/test/resources/atlas.json b/packages/text-bitmap/test/resources/atlas.json new file mode 100644 index 0000000..86e65a5 --- /dev/null +++ b/packages/text-bitmap/test/resources/atlas.json @@ -0,0 +1,25 @@ +{ + "meta": { + "image": "atlas.png", + "size": {"w":256,"h":256}, + "scale": "1" + }, + "frames": { + "resources/test.png": + { + "frame": {"x":2,"y":2,"w":152,"h":188}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, + "sourceSize": {"w":300,"h":225} + }, + "resources/font.png": + { + "frame": {"x":158,"y":2,"w":40,"h":42}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, + "sourceSize": {"w":43,"h":46} + } + } +} \ No newline at end of file diff --git a/packages/text-bitmap/test/resources/atlas.png b/packages/text-bitmap/test/resources/atlas.png new file mode 100644 index 0000000..d5e7892 --- /dev/null +++ b/packages/text-bitmap/test/resources/atlas.png Binary files differ diff --git a/packages/text-bitmap/test/resources/atlas@0.5x.json b/packages/text-bitmap/test/resources/atlas@0.5x.json new file mode 100644 index 0000000..ae990a1 --- /dev/null +++ b/packages/text-bitmap/test/resources/atlas@0.5x.json @@ -0,0 +1,25 @@ +{ + "meta": { + "image": "atlas@0.5x.png", + "size": {"w":256,"h":256}, + "scale": "1" + }, + "frames": { + "resources/test.png": + { + "frame": {"x":2,"y":2,"w":152,"h":188}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, + "sourceSize": {"w":300,"h":225} + }, + "resources/font.png": + { + "frame": {"x":158,"y":2,"w":40,"h":42}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, + "sourceSize": {"w":43,"h":46} + } + } +} \ No newline at end of file diff --git a/packages/text-bitmap/test/resources/atlas@0.5x.png b/packages/text-bitmap/test/resources/atlas@0.5x.png new file mode 100644 index 0000000..d5e7892 --- /dev/null +++ b/packages/text-bitmap/test/resources/atlas@0.5x.png Binary files differ diff --git a/packages/text-bitmap/test/resources/font.fnt b/packages/text-bitmap/test/resources/font.fnt new file mode 100644 index 0000000..56e1060 --- /dev/null +++ b/packages/text-bitmap/test/resources/font.fnt @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/text-bitmap/test/resources/font.png b/packages/text-bitmap/test/resources/font.png new file mode 100644 index 0000000..cf772e9 --- /dev/null +++ b/packages/text-bitmap/test/resources/font.png Binary files differ diff --git a/packages/text-bitmap/test/resources/font@0.5x.fnt b/packages/text-bitmap/test/resources/font@0.5x.fnt new file mode 100644 index 0000000..6c247c7 --- /dev/null +++ b/packages/text-bitmap/test/resources/font@0.5x.fnt @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/text-bitmap/test/resources/font@0.5x.png b/packages/text-bitmap/test/resources/font@0.5x.png new file mode 100644 index 0000000..cf772e9 --- /dev/null +++ b/packages/text-bitmap/test/resources/font@0.5x.png Binary files differ