soiz1's picture
Upload 811 files
30c32c8 verified
raw
history blame
6.1 kB
const EventEmitter = require('events');
const AssetUtil = require('../util/tw-asset-util');
const StringUtil = require('../util/string-util');
const log = require('../util/log');
/**
* @typedef InternalFont
* @property {boolean} system True if the font is built in to the system
* @property {string} family The font's name
* @property {string} fallback Fallback font family list
* @property {Asset} [asset] scratch-storage asset if system: false
*/
class FontManager extends EventEmitter {
/**
* @param {Runtime} runtime
*/
constructor(runtime) {
super();
this.runtime = runtime;
/** @type {Array<InternalFont>} */
this.fonts = [];
}
/**
* @param {string} family An unknown font family
* @returns {boolean} true if the family is valid
*/
isValidFamily(family) {
return /^[-\w ]+$/.test(family);
}
/**
* @param {string} family
* @returns {boolean}
*/
hasFont(family) {
return !!this.fonts.find(i => i.family === family);
}
/**
* @param {string} family
* @returns {boolean}
*/
getSafeName(family) {
family = family.replace(/[^-\w ]/g, '');
return StringUtil.unusedName(family, this.fonts.map(i => i.family));
}
changed() {
this.emit('change');
}
/**
* @param {string} family
* @param {string} fallback
*/
addSystemFont(family, fallback) {
if (!this.isValidFamily(family)) {
throw new Error('Invalid family');
}
this.fonts.push({
system: true,
family,
fallback
});
this.changed();
}
/**
* @param {string} family
* @param {string} fallback
* @param {Asset} asset scratch-storage asset
*/
addCustomFont(family, fallback, asset) {
if (!this.isValidFamily(family)) {
throw new Error('Invalid family');
}
this.fonts.push({
system: false,
family,
fallback,
asset
});
this.updateRenderer();
this.changed();
}
/**
* @returns {Array<{system: boolean; name: string; family: string; data: Uint8Array | null; format: string | null}>}
*/
getFonts() {
return this.fonts.map(font => ({
system: font.system,
name: font.family,
family: `"${font.family}", ${font.fallback}`,
data: font.asset ? font.asset.data : null,
format: font.asset ? font.asset.dataFormat : null
}));
}
/**
* @param {number} index Corresponds to index from getFonts()
*/
deleteFont(index) {
const [removed] = this.fonts.splice(index, 1);
if (!removed.system) {
this.updateRenderer();
}
this.changed();
}
clear() {
const hadNonSystemFont = this.fonts.some(i => !i.system);
this.fonts = [];
if (hadNonSystemFont) {
this.updateRenderer();
}
this.changed();
}
updateRenderer() {
if (!this.runtime.renderer || !this.runtime.renderer.setCustomFonts) {
return;
}
const fontfaces = {};
for (const font of this.fonts) {
if (!font.system) {
const uri = font.asset.encodeDataURI();
const fontface = `@font-face { font-family: "${font.family}"; src: url("${uri}"); }`;
const family = `"${font.family}", ${font.fallback}`;
fontfaces[family] = fontface;
}
}
this.runtime.renderer.setCustomFonts(fontfaces);
}
/**
* Get data to save in project.json and sb3 files.
*/
serializeJSON() {
if (this.fonts.length === 0) {
return null;
}
return this.fonts.map(font => {
const serialized = {
system: font.system,
family: font.family,
fallback: font.fallback
};
if (!font.system) {
const asset = font.asset;
serialized.md5ext = `${asset.assetId}.${asset.dataFormat}`;
}
return serialized;
});
}
/**
* @returns {Asset[]} list of scratch-storage assets
*/
serializeAssets() {
return this.fonts
.filter(i => !i.system)
.map(i => i.asset);
}
/**
* @param {unknown} json
* @param {JSZip} [zip]
* @param {boolean} [keepExisting]
* @returns {Promise<void>}
*/
async deserialize(json, zip, keepExisting) {
if (!keepExisting) {
this.clear();
}
if (!Array.isArray(json)) {
return;
}
for (const font of json) {
if (!font || typeof font !== 'object') {
continue;
}
try {
const system = font.system;
const family = font.family;
const fallback = font.fallback;
if (
typeof system !== 'boolean' ||
typeof family !== 'string' ||
typeof fallback !== 'string' ||
this.hasFont(family)
) {
continue;
}
if (system) {
this.addSystemFont(family, fallback);
} else {
const md5ext = font.md5ext;
if (typeof md5ext !== 'string') {
continue;
}
const asset = await AssetUtil.getByMd5ext(
this.runtime,
zip,
this.runtime.storage.AssetType.Font,
md5ext
);
this.addCustomFont(family, fallback, asset);
}
} catch (e) {
log.error('could not add font', e);
}
}
}
}
module.exports = FontManager;