/** **************************************************************************** * Spine Runtimes License Agreement * Last updated July 28, 2023. Replaces all prior versions. * * Copyright (c) 2013-2023, Esoteric Software LLC * * Integration of the Spine Runtimes into software or otherwise creating * derivative works of the Spine Runtimes is permitted under the terms and * conditions of Section 2 of the Spine Editor License Agreement: * http://esotericsoftware.com/spine-editor-license * * Otherwise, it is permitted to integrate the Spine Runtimes into software or * otherwise create derivative works of the Spine Runtimes (collectively, * "Products"), provided that each user of the Products must obtain their own * Spine Editor license and redistribution of the Products in any form must * include this license and copyright notice. * * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ import { checkExtension, copySearchParams, DOMAdapter, extensions, ExtensionType, LoaderParserPriority, path, Resolver, TextureSource } from 'pixi.js'; import { SpineTexture } from '../SpineTexture.js'; import { TextureAtlas } from '@esotericsoftware/spine-core'; import type { AssetExtension, Loader, ResolvedAsset, Texture, UnresolvedAsset } from 'pixi.js'; type RawAtlas = string; const spineTextureAtlasLoader: AssetExtension = { extension: ExtensionType.Asset, resolver: { test: (value: string): boolean => checkExtension(value, ".atlas"), parse: (value: string): UnresolvedAsset => { const split = value.split('.'); return { resolution: parseFloat(Resolver.RETINA_PREFIX?.exec(value)?.[1] ?? '1'), format: split[split.length - 2], src: value, }; }, }, loader: { extension: { type: ExtensionType.LoadParser, priority: LoaderParserPriority.Normal, name: 'spineTextureAtlasLoader', }, test (url: string): boolean { return checkExtension(url, '.atlas'); }, async load (url: string): Promise { const response = await DOMAdapter.get().fetch(url); const txt = await response.text(); return txt; }, testParse (asset: unknown, options: ResolvedAsset): Promise { const isExtensionRight = checkExtension(options.src as string, '.atlas'); const isString = typeof asset === 'string'; return Promise.resolve(isExtensionRight && isString); }, unload (atlas: TextureAtlas) { atlas.dispose(); }, async parse (asset: RawAtlas, options: ResolvedAsset, loader: Loader): Promise { const metadata: ISpineAtlasMetadata = options.data || {}; let basePath = path.dirname(options.src as string); if (basePath && basePath.lastIndexOf('/') !== basePath.length - 1) { basePath += '/'; } // Retval is going to be a texture atlas. However we need to wait for it's callback to resolve this promise. const retval = new TextureAtlas(asset); // If the user gave me only one texture, that one is assumed to be the "first" texture in the atlas if (metadata.images instanceof TextureSource || typeof metadata.images === 'string') { const pixiTexture = metadata.images; metadata.images = {} as Record; metadata.images[retval.pages[0].name] = pixiTexture; } // we will wait for all promises for the textures at the same time at the end. const textureLoadingPromises: Promise[] = []; // fill the pages for (const page of retval.pages) { const pageName = page.name; const providedPage = metadata?.images ? metadata.images[pageName] : undefined; if (providedPage instanceof TextureSource) { page.setTexture(SpineTexture.from(providedPage)); } else { // eslint-disable-next-line max-len const url: string = providedPage ?? path.normalize([...basePath.split(path.sep), pageName].join(path.sep)); const assetsToLoadIn = { src: copySearchParams(url, options.src as string), data: { ...metadata.imageMetadata, alphaMode: page.pma ? 'premultiplied-alpha' : 'premultiply-alpha-on-upload' } }; const pixiPromise = loader.load(assetsToLoadIn).then((texture) => { page.setTexture(SpineTexture.from(texture.source)); }); textureLoadingPromises.push(pixiPromise); } } await Promise.all(textureLoadingPromises); return retval; }, }, } as AssetExtension; extensions.add(spineTextureAtlasLoader); export interface ISpineAtlasMetadata { // If you are downloading an .atlas file, this metadata will go to the Texture loader 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?: TextureSource | string | Record; }