Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support loading font assets in data URLs #8898

Merged
merged 2 commits into from Nov 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 12 additions & 6 deletions packages/assets/src/loader/parsers/loadWebFont.ts
@@ -1,13 +1,22 @@
import { extensions, ExtensionType, settings, utils } from '@pixi/core';
import { checkDataUrl } from '../../utils/checkDataUrl';
import { checkExtension } from '../../utils/checkExtension';
import { LoaderParserPriority } from './LoaderParser';

import type { LoadAsset } from '../types';
import type { LoaderParser } from './LoaderParser';

const validWeights = ['normal', 'bold',
const validWeights = [
'normal', 'bold',
'100', '200', '300', '400', '500', '600', '700', '800', '900',
];
const validFonts = ['woff', 'woff2', 'ttf', 'otf'];
const validFontExtensions = ['.ttf', '.otf', '.woff', '.woff2'];
const validFontMIMEs = [
'font/ttf',
'font/otf',
'font/woff',
'font/woff2',
];

export type LoadFontData = {
family: string;
Expand Down Expand Up @@ -51,10 +60,7 @@ export const loadWebFont = {

test(url: string): boolean
{
const tempURL = url.split('?')[0];
const extension = tempURL.split('.').pop();

return validFonts.includes(extension);
return checkDataUrl(url, validFontMIMEs) || checkExtension(url, validFontExtensions);
},

async load(url: string, options?: LoadAsset<LoadFontData>): Promise<FontFace | FontFace[]>
Expand Down
24 changes: 10 additions & 14 deletions packages/assets/src/loader/parsers/textures/loadTextures.ts
@@ -1,15 +1,22 @@
import { BaseTexture, extensions, ExtensionType, settings, utils } from '@pixi/core';
import { checkDataUrl } from '../../../utils/checkDataUrl';
import { checkExtension } from '../../../utils/checkExtension';
import { LoaderParserPriority } from '../LoaderParser';
import { WorkerManager } from '../WorkerManager';
import { checkExtension } from './utils/checkExtension';
import { createTexture } from './utils/createTexture';

import type { IBaseTextureOptions, Texture } from '@pixi/core';
import type { Loader } from '../../Loader';
import type { LoadAsset } from '../../types';
import type { LoaderParser } from '../LoaderParser';

const validImages = ['.jpg', '.png', '.jpeg', '.avif', '.webp'];
const validImageExtensions = ['.jpeg', '.jpg', '.png', '.webp', '.avif'];
const validImageMIMEs = [
'image/jpeg',
'image/png',
'image/webp',
'image/avif',
];

/**
* Returns a promise that resolves an ImageBitmaps.
Expand Down Expand Up @@ -51,18 +58,7 @@ export const loadTextures = {

test(url: string): boolean
{
let isValidBase64Suffix = false;

for (let i = 0; i < validImages.length; i++)
{
if (url.startsWith(`data:image/${validImages[i].slice(1)}`))
{
isValidBase64Suffix = true;
break;
}
}

return isValidBase64Suffix || checkExtension(url, validImages);
return checkDataUrl(url, validImageMIMEs) || checkExtension(url, validImageExtensions);
},

async load(url: string, asset: LoadAsset<IBaseTextureOptions>, loader: Loader): Promise<Texture>
Expand Down
1 change: 0 additions & 1 deletion packages/assets/src/loader/parsers/textures/utils/index.ts
@@ -1,2 +1 @@
export * from './checkExtension';
export * from './createTexture';
14 changes: 14 additions & 0 deletions packages/assets/src/utils/checkDataUrl.ts
@@ -0,0 +1,14 @@
export function checkDataUrl(url: string, mimes: string | string[]): boolean
{
if (Array.isArray(mimes))
{
for (const mime of mimes)
{
if (url.startsWith(`data:${mime}`)) return true;
}

return false;
}

return url.startsWith(`data:${mimes}`);
}
2 changes: 2 additions & 0 deletions packages/assets/src/utils/index.ts
@@ -1,3 +1,5 @@
export * from './checkDataUrl';
export * from './checkExtension';
export * from './convertToList';
export * from './createStringVariations';
export * from './isSingleItem';
71 changes: 51 additions & 20 deletions packages/assets/test/assets.tests.ts
Expand Up @@ -333,31 +333,62 @@ describe('Assets', () =>
expect(assets.bunny.baseTexture).toBe(null);
});

it('should load PNG base64 assets', async () =>
it('should load PNG assets from data URL', async () =>
{
// Other formats (JPG, JPEG, WEBP, AVIF) can be added similarly.
let bunnyBase64 = `
data:image/png;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAkGBggGBQkIBwgKCQkKDRYODQwMDRoTFBAWHxwhIB8c
Hh4jJzIqIyUvJR4eKzssLzM1ODg4ISo9QTw2QTI3ODX/2wBDAQkKCg0LDRkODhk1JB4kNTU1NTU1NTU1NTU1NTU1NTU1NTU1NT
U1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTX/wgARCAAlABoDAREAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAABgUHBP/E
ABkBAAMBAQEAAAAAAAAAAAAAAAIDBAUBBv/aAAwDAQACEAMQAAAAfoMjG9G5byxWYwUHfPaNTTmebMAqZ1zJphvFntQwkNP49f
dWrUdeIEs1xcBAWmGH/8QANRAAAQMCBAQDAg8AAAAAAAAAAgEDBAURAAYSEwcUIWEQFSIxUjI0NkJFVFVicXWBoqTCw//aAAgB
AQABPwDiRsHGobM3rBfqoBKb+abaNOlYk/Eb4qz8Cmee0mhjsQpnJIwywC7J6XV5nsl27It/h4y6xSIHEFlnLjLLEN+mPG8DCa
BMxcZ037oh+Gd810nMgQIlIkrKciTleds0aBoRp0LoappLqSexcMsTtp0xjPSmWiu6+KtiLaGSoKKikirbsmMpVFqhV2NLqLjp
gEN5g3WoxuKpkTCp6ARVS+2eEz7lv7bgB9x19AMeyivVFxxGyrScswoE6jxliPvz9o7PHo0K06VkBV0j1HFAybCqFGalSTdU3e
vpW1sUygRJ+fjo07dditRX3LA8bSqYG0KLcFRfYZYHIGWg+g4B93GdZfqS9Vxxk+T9J/M/8H8QuKjNBY5BX4jmz7xpcMZCqPm3
EMZmtHN+myTund1jw4zhbIwzvqEsH9Hv3Qm/74oFO8oy5TqfubvKRWmNdratIoN7YyRTdji/mj1/E9f8lxHv228P/8QAHxEAAg
IDAAMBAQAAAAAAAAAAAQIAAwQREgUQIRRR/9oACAECAQE/AL2CoWJi5VfJJaY1osJ0dzqZ1qikgwVJYpcHWp426sM4Hydj+zMr
U0kzGwBbV0TPHY6qz7nCzL+0tqUfqRNBfk8cGBfv1YNrqARF0xPr/8QAHhEAAgICAwEBAAAAAAAAAAAAAQIAAxESBBAhMTL/2g
AIAQMBAT8AQEnAgqckSxWA9646HfML6kLOUjEAzQyh23xLeQVbAl9hKjE3Mp/Yli1M32cnGAB0h9hhPnX/2Q==
// Other formats (JPEG, WEBP, AVIF) can be added similarly.
let bunnyDataURL = `
data:image/png;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAkGBggGBQkIBwgKCQkKDRYODQwMDRoTFBAWHxwhIB8cHh
4jJzIqIyUvJR4eKzssLzM1ODg4ISo9QTw2QTI3ODX/2wBDAQkKCg0LDRkODhk1JB4kNTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NT
U1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTX/wgARCAAlABoDAREAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAABgUHBP/EABkBAA
MBAQEAAAAAAAAAAAAAAAIDBAUBBv/aAAwDAQACEAMQAAAAfoMjG9G5byxWYwUHfPaNTTmebMAqZ1zJphvFntQwkNP49fdWrUdeIE
s1xcBAWmGH/8QANRAAAQMCBAQDAg8AAAAAAAAAAgEDBAURAAYSEwcUIWEQFSIxUjI0NkJFVFVicXWBoqTCw//aAAgBAQABPwDiRs
HGobM3rBfqoBKb+abaNOlYk/Eb4qz8Cmee0mhjsQpnJIwywC7J6XV5nsl27It/h4y6xSIHEFlnLjLLEN+mPG8DCaBMxcZ037oh+G
d810nMgQIlIkrKciTleds0aBoRp0LoappLqSexcMsTtp0xjPSmWiu6+KtiLaGSoKKikirbsmMpVFqhV2NLqLjpgEN5g3WoxuKpkT
Cp6ARVS+2eEz7lv7bgB9x19AMeyivVFxxGyrScswoE6jxliPvz9o7PHo0K06VkBV0j1HFAybCqFGalSTdU3evpW1sUygRJ+fjo07
dditRX3LA8bSqYG0KLcFRfYZYHIGWg+g4B93GdZfqS9Vxxk+T9J/M/8H8QuKjNBY5BX4jmz7xpcMZCqPm3EMZmtHN+myTund1jw4
zhbIwzvqEsH9Hv3Qm/74oFO8oy5TqfubvKRWmNdratIoN7YyRTdji/mj1/E9f8lxHv228P/8QAHxEAAgIDAAMBAQAAAAAAAAAAAQ
IAAwQREgUQIRRR/9oACAECAQE/AL2CoWJi5VfJJaY1osJ0dzqZ1qikgwVJYpcHWp426sM4Hydj+zMrU0kzGwBbV0TPHY6qz7nCzL
+0tqUfqRNBfk8cGBfv1YNrqARF0xPr/8QAHhEAAgICAwEBAAAAAAAAAAAAAQIAAxESBBAhMTL/2gAIAQMBAT8AQEnAgqckSxWA96
46HfML6kLOUjEAzQyh23xLeQVbAl9hKjE3Mp/Yli1M32cnGAB0h9hhPnX/2Q==
`;

// to prevent eslint max-len warning
bunnyBase64 = bunnyBase64.replace(/\s/g, '');
// Prevent eslint max-len warning
bunnyDataURL = bunnyDataURL.replace(/\s/g, '');

Assets.add('bunny', bunnyBase64);
const bunny = await Assets.load('bunny');
const bunny = await Assets.load(bunnyDataURL);

expect(bunny).toBeInstanceOf(Texture);
});

it('should load TTF assets from data URL', async () =>
{
// Smile icon from IcoMoon (https://icomoon.io/#icons-icomoon), CC BY 4.0
let fontDataURL = `
data:font/ttf;base64,AAEAAAALAIAAAwAwT1MvMg8SDCkAAAC8AAAAYGNtYXAARu8mAAABHAAAAJhnYXNwAAAAEAAAAbQAAAA
IZ2x5ZoDPYX8AAAG8AAABaGhlYWQib6aFAAADJAAAADZoaGVhB8IDxgAAA1wAAAAkaG10eAoAAAAAAAOAAAAAFGxvY2EAKADIAAA
DlAAAAAxtYXhwAAsAaAAAA6AAAAAgbmFtZZlKCfsAAAPAAAABhnBvc3QAAwAAAAAFSAAAACAAAwMAAZAABQAAApkCzAAAAI8CmQL
MAAAB6wAzAQkAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAABAAAD//wPA/8AAQAPAAEAAAAABAAAAAAAAAAAAAAAgAAA
AAAAGAAAAAwAAADQAAAAEAAAAZAABAAMAAAA0AAEABAAAAGQAAwABAAAANAADAAoAAABkAAQAMAAAAAgACAACAAAAAQAg//3//wA
AAAAAIP/9//8AAf/jAAMAAQAAAAAAAAAAAAwAAAAAADQAAAAAAAAAAwAAAAAAAAABAAAAAQAAACAAAAAgAAAAAwAB9kIAAfZCAAA
ABAABAAH//wAPAAEAAAAAAAAAAAACAAA3OQEAAAAAAQAAAAAAAAAAAAIAADc5AQAAAAABAAAAAAAAAAAAAgAANzkBAAAAAAUAAP/
ABAADwAAbADcAQwBPAGUAAAUyNz4BNzY1NCcuAScmIyIHDgEHBhUUFx4BFxYTMhceARcWFRQHDgEHBiMiJy4BJyY1NDc+ATc2BzQ
2MzIWFRQGIyImJTQ2MzIWFRQGIyImExcGBw4BBwYjIicuAScmJzceATMyNgIAal1eiygoKCiLXl1qal1eiygoKCiLXl1qVkxMcSA
hISBxTExWVkxMcSAhISBxTEyqJRsbJSUbGyUBgCUbGyUlGxslQFIVHR1GKSgsLCgpRh0dFVIdZj09ZkAoKIteXWpqXV6LKCgoKIt
eXWpqXV6LKCgDoCEgcUxMVlZMTHEgISEgcUxMVlZMTHEgIeAbJSUbGyUlGxslJRsbJSX+6DIjHB0pCwsLCykdHCMyMTw8AAEAAAA
BAACCgfDXXw889QALBAAAAAAA36gxBAAAAADfqDEEAAD/wAQAA8AAAAAIAAIAAAAAAAAAAQAAA8D/wAAABAAAAAAABAAAAQAAAAA
AAAAAAAAAAAAAAAUEAAAAAAAAAAAAAAACAAAABAAAAAAAAAAACgAUAB4AtAABAAAABQBmAAUAAAAAAAIAAAAAAAAAAAAAAAAAAAA
AAAAADgCuAAEAAAAAAAEABwAAAAEAAAAAAAIABwBgAAEAAAAAAAMABwA2AAEAAAAAAAQABwB1AAEAAAAAAAUACwAVAAEAAAAAAAY
ABwBLAAEAAAAAAAoAGgCKAAMAAQQJAAEADgAHAAMAAQQJAAIADgBnAAMAAQQJAAMADgA9AAMAAQQJAAQADgB8AAMAAQQJAAUAFgA
gAAMAAQQJAAYADgBSAAMAAQQJAAoANACkaWNvbW9vbgBpAGMAbwBtAG8AbwBuVmVyc2lvbiAxLjAAVgBlAHIAcwBpAG8AbgAgADE
ALgAwaWNvbW9vbgBpAGMAbwBtAG8AbwBuaWNvbW9vbgBpAGMAbwBtAG8AbwBuUmVndWxhcgBSAGUAZwB1AGwAYQByaWNvbW9vbgB
pAGMAbwBtAG8AbwBuRm9udCBnZW5lcmF0ZWQgYnkgSWNvTW9vbi4ARgBvAG4AdAAgAGcAZQBuAGUAcgBhAHQAZQBkACAAYgB5ACA
ASQBjAG8ATQBvAG8AbgAuAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
`;

fontDataURL = fontDataURL.replace(/\s/g, '');

const font = await Assets.load(fontDataURL);

expect(font).toBeInstanceOf(FontFace);
});
});