From 22e79985279eb2f767715180a32648761ba20ad5 Mon Sep 17 00:00:00 2001 From: Z Date: Thu, 25 May 2023 17:13:35 -0400 Subject: [PATCH 1/2] feat: add support for data url loading --- .../loader-base/src/SpineLoaderAbstract.ts | 44 ++++++++++++++----- packages/loader-base/src/atlasLoader.ts | 4 +- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/packages/loader-base/src/SpineLoaderAbstract.ts b/packages/loader-base/src/SpineLoaderAbstract.ts index de54a14d..0d379091 100644 --- a/packages/loader-base/src/SpineLoaderAbstract.ts +++ b/packages/loader-base/src/SpineLoaderAbstract.ts @@ -1,11 +1,19 @@ import { ISkeletonData, ISkeletonParser, TextureAtlas } from '@pixi-spine/base'; -import { AssetExtension, checkExtension, LoadAsset, Loader, LoaderParserPriority } from '@pixi/assets'; +import { AssetExtension, checkDataUrl, checkExtension, LoadAsset, Loader, LoaderParserPriority } from '@pixi/assets'; import { BaseTexture, extensions, ExtensionType, settings, Texture, utils } from '@pixi/core'; import { makeSpineTextureAtlasLoaderFunctionFromPixiLoaderObject } from './atlasLoader'; type SPINEJSON = any; type SPINEBINARY = ArrayBuffer; +const validJSONExtension = '.json'; + +const validJSONMIME = 'application/json'; + +const validAtlasMIME = 'application/octet-stream'; + +const validImageMIMEs = ['image/jpeg', 'image/png']; + function isJson(resource: unknown): resource is SPINEJSON { return resource.hasOwnProperty('bones'); } @@ -55,7 +63,7 @@ export abstract class SpineLoaderAbstract { // #region Parsing spine data testParse(asset: unknown, options: LoadAsset): Promise { - const isJsonSpineModel = checkExtension(options.src, '.json') && isJson(asset); + const isJsonSpineModel = checkDataUrl(options.src, validJSONMIME) || (checkExtension(options.src, validJSONExtension) && isJson(asset)); const isBinarySpineModel = checkExtension(options.src, '.skel') && isBuffer(asset); // From 6.x loader. If the atlas is strictly false we bail @@ -73,7 +81,7 @@ export abstract class SpineLoaderAbstract { basePath += '/'; } - const isJsonSpineModel = checkExtension(loadAsset.src, '.json') && isJson(asset); + const isJsonSpineModel = checkDataUrl(loadAsset.src, validJSONMIME) || (checkExtension(loadAsset.src, validJSONExtension) && isJson(asset)); // const isBinarySpineModel = fileExt === 'slel' && isBuffer(asset); let parser: ISkeletonParser = null; @@ -101,8 +109,15 @@ export abstract class SpineLoaderAbstract { } // if for some odd reason, you dumped the text information of the atlas into the metadata... - const textAtlas = metadata.atlasRawData; + let textAtlas = metadata.atlasRawData; + // Maybe you passed a data URL to instead of a path + const isSpineAtlasFileURL = checkDataUrl(metadata.spineAtlasFile, validAtlasMIME); + + // If it's an URL then decode it and assign it to textAtlas + if (isSpineAtlasFileURL) { + textAtlas = atob(metadata.spineAtlasFile.split(',')[1]); + } if (textAtlas) { let auxResolve = null; let auxReject = null; @@ -110,12 +125,17 @@ export abstract class SpineLoaderAbstract { auxResolve = resolve; auxReject = reject; }); - const atlas = new TextureAtlas(textAtlas, makeSpineTextureAtlasLoaderFunctionFromPixiLoaderObject(loader, basePath, metadata.imageMetadata), (newAtlas) => { - if (!newAtlas) { - auxReject('Something went terribly wrong loading a spine .atlas file\nMost likely your texture failed to load.'); + const imageURL = typeof metadata.image === 'string' && checkDataUrl(metadata.image, validImageMIMEs) ? metadata.image : null; + const atlas = new TextureAtlas( + textAtlas, + makeSpineTextureAtlasLoaderFunctionFromPixiLoaderObject(loader, basePath, metadata.imageMetadata, imageURL), + (newAtlas) => { + if (!newAtlas) { + auxReject('Something went terribly wrong loading a spine .atlas file\nMost likely your texture failed to load.'); + } + auxResolve(atlas); } - auxResolve(atlas); - }); + ); const textureAtlas = await atlasPromise; return spineAdapter.parseData(parser, textureAtlas, dataToParse); @@ -168,7 +188,7 @@ export interface ISpineMetadata { spineAtlas?: Partial; // If you are going to download an .atlas file, you can specify an alias here for cache/future lookup spineAtlasAlias?: string[]; - // If you want to use a custom .atlas file, you can specify the path here. **It must be a .atlas file or you need your own parser!** + // If you want to use a custom .atlas file or data URL, you can specify the path here. **It must be a .atlas file or you need your own parser!** spineAtlasFile?: string; // If for some reason, you have the raw text content of an .atlas file, and want to use it dump it here atlasRawData?: string; @@ -178,6 +198,6 @@ export interface ISpineMetadata { imageMetadata?: any; // If you already have atlas pages loaded as pixi textures and want to use that to create the atlas, you can pass them here images?: Record; - // If your spine only uses one atlas page and you have it as a pixi texture, you can pass it here - image?: Texture | BaseTexture; + // If your spine only uses one atlas page and you have it as a pixi texture or data URL, you can pass it here + image?: Texture | BaseTexture | string; } diff --git a/packages/loader-base/src/atlasLoader.ts b/packages/loader-base/src/atlasLoader.ts index 454cc3c1..d25cb214 100644 --- a/packages/loader-base/src/atlasLoader.ts +++ b/packages/loader-base/src/atlasLoader.ts @@ -95,13 +95,13 @@ const spineTextureAtlasLoader: AssetExtension { +export const makeSpineTextureAtlasLoaderFunctionFromPixiLoaderObject = (loader: Loader, atlasBasePath: string, imageMetadata: any, imageURL?:string) => { return async (pageName: string, textureLoadedCallback: (tex: BaseTexture) => any): Promise => { // const url = utils.path.join(...atlasBasePath.split(utils.path.sep), pageName); // Broken in upstream const url = utils.path.normalize([...atlasBasePath.split(utils.path.sep), pageName].join(utils.path.sep)); - const texture = await loader.load({ src: url, data: imageMetadata }); + const texture = await loader.load(imageURL? imageURL:{ src: url, data: imageMetadata }); textureLoadedCallback(texture.baseTexture); }; From e6a6b867d0d6e9d0e3748a12017a0aad9738371b Mon Sep 17 00:00:00 2001 From: Z Date: Fri, 26 May 2023 12:39:45 -0400 Subject: [PATCH 2/2] fix: add text/plain MIME to validAtlasMIMEs --- packages/loader-base/src/SpineLoaderAbstract.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/loader-base/src/SpineLoaderAbstract.ts b/packages/loader-base/src/SpineLoaderAbstract.ts index 0d379091..05dcd5f1 100644 --- a/packages/loader-base/src/SpineLoaderAbstract.ts +++ b/packages/loader-base/src/SpineLoaderAbstract.ts @@ -10,7 +10,7 @@ const validJSONExtension = '.json'; const validJSONMIME = 'application/json'; -const validAtlasMIME = 'application/octet-stream'; +const validAtlasMIMEs = ['application/octet-stream', 'text/plain']; const validImageMIMEs = ['image/jpeg', 'image/png']; @@ -112,7 +112,7 @@ export abstract class SpineLoaderAbstract { let textAtlas = metadata.atlasRawData; // Maybe you passed a data URL to instead of a path - const isSpineAtlasFileURL = checkDataUrl(metadata.spineAtlasFile, validAtlasMIME); + const isSpineAtlasFileURL = checkDataUrl(metadata.spineAtlasFile, validAtlasMIMEs); // If it's an URL then decode it and assign it to textAtlas if (isSpineAtlasFileURL) {