import { Cache } from '../../assets/cache/Cache';
import { deprecation, v8_0_0 } from '../../utils/logging/deprecation';
import { warn } from '../../utils/logging/warn';
import { TextStyle } from '../text/TextStyle';
import { DynamicBitmapFont } from './DynamicBitmapFont';
import { getBitmapTextLayout } from './utils/getBitmapTextLayout';
import { resolveCharacters } from './utils/resolveCharacters';
import type { TextStyleOptions } from '../text/TextStyle';
import type { BitmapFont } from './BitmapFont';
import type { BitmapTextLayoutData } from './utils/getBitmapTextLayout';
let fontCount = 0;
/**
*
* The options for installing a new BitmapFont. Once installed the font will be available for use in the BitmapText.
* It can be accessed by the `fontFamily` property of the TextStyle.
*
* Install a new BitmapFont will create the characters provided for the font and store them in the cache.
* But don't worry, if a character is requested that hasn't been generated yet, it will be created on the fly.
* @memberof text
*/
export interface BitmapFontInstallOptions
{
/** the name of the font, this will be the name you use in the fontFamily of text style to access this font */
name?: string;
/**
* Characters included in the font set. You can also use ranges.
* For example, `[['a', 'z'], ['A', 'Z'], "!@#$%^&*()~{}[] "]`.
* Don't forget to include spaces ' ' in your character set!
* @default BitmapFont.ALPHANUMERIC
*/
chars?: string | (string | string[])[];
/**
* Render resolution for glyphs.
* @default 1
*/
resolution?: number;
/**
* Padding between glyphs on texture atlas. Lower values could mean more visual artifacts
* and bleeding from other glyphs, larger values increase the space required on the texture.
* @default 4
*/
padding?: number;
/**
* Skip generation of kerning information for the BitmapFont.
* If true, this could potentially increase the performance, but may impact the rendered text appearance.
* @default false
*/
skipKerning?: boolean;
/** Style options to render with BitmapFont. */
style?: TextStyle | TextStyleOptions;
}
/**
* The BitmapFontManager is a helper that exists to install and uninstall fonts
* into the cache for BitmapText objects.
* @memberof text
* @name BitmapFontManager
* @example
* import { BitmapFontManager, BitmapText } from 'pixi.js';
*
* BitmapFontManager.install({
* name: 'TitleFont',
* style: {}
* });
*
* const title = new BitmapText({ text: 'This is the title', style: { fontFamily: 'TitleFont' }});
*/
class BitmapFontManagerClass
{
/**
* This character set includes all the letters in the alphabet (both lower- and upper- case).
* @type {string[][]}
* @example
* BitmapFont.from('ExampleFont', style, { chars: BitmapFont.ALPHA })
*/
public readonly ALPHA = [['a', 'z'], ['A', 'Z'], ' '];
/**
* This character set includes all decimal digits (from 0 to 9).
* @type {string[][]}
* @example
* BitmapFont.from('ExampleFont', style, { chars: BitmapFont.NUMERIC })
*/
public readonly NUMERIC = [['0', '9']];
/**
* This character set is the union of `BitmapFont.ALPHA` and `BitmapFont.NUMERIC`.
* @type {string[][]}
*/
public readonly ALPHANUMERIC = [['a', 'z'], ['A', 'Z'], ['0', '9'], ' '];
/**
* This character set consists of all the ASCII table.
* @member {string[][]}
* @see http://www.asciitable.com/
*/
public readonly ASCII = [[' ', '~']];
/** Default options for installing a new BitmapFont. */
public defaultOptions: Omit<BitmapFontInstallOptions, 'style'> = {
chars: this.ALPHANUMERIC,
resolution: 1,
padding: 4,
skipKerning: false,
};
/**
* Get a font for the specified text and style.
* @param text - The text to get the font for
* @param style - The style to use
*/
public getFont(text: string, style: TextStyle): BitmapFont
{
let fontFamilyKey = `${style.fontFamily as string}-bitmap`;
let overrideFill = true;
// assuming there is no texture we can use a tint!
if (style._fill.fill && !style._stroke)
{
fontFamilyKey += style._fill.fill.styleKey;
overrideFill = false;
}
else if (style._stroke || style.dropShadow)
{
// if there is a stoke, we need to use the style key as this the font generated cannot be tinted
// due to the fact the font has at least two colors.
let key = style.styleKey;
// remove the font size..
key = key.substring(0, key.lastIndexOf('-'));
fontFamilyKey = `${key}-bitmap`;
overrideFill = false;
}
// first get us the the right font...
if (!Cache.has(fontFamilyKey))
{
const fnt = new DynamicBitmapFont({
style,
overrideFill,
overrideSize: true,
...this.defaultOptions,
});
fontCount++;
// warn users if they have created too many dynamic fonts
if (fontCount > 50)
{
// eslint-disable-next-line max-len
warn('BitmapText', `You have dynamically created ${fontCount} bitmap fonts, this can be inefficient. Try pre installing your font styles using \`BitmapFont.install({name:"style1", style})\``);
}
fnt.once('destroy', () =>
{
fontCount--;
Cache.remove(fontFamilyKey);
});
Cache.set(
fontFamilyKey as string,
fnt
);
}
const dynamicFont = Cache.get(fontFamilyKey);
(dynamicFont as DynamicBitmapFont).ensureCharacters?.(text);
return dynamicFont;
}
/**
* Get the layout of a text for the specified style.
* @param text - The text to get the layout for
* @param style - The style to use
*/
public getLayout(text: string, style: TextStyle): BitmapTextLayoutData
{
const bitmapFont = this.getFont(text, style);
return getBitmapTextLayout([...text], style, bitmapFont);
}
/**
* Measure the text using the specified style.
* @param text - The text to measure
* @param style - The style to use
*/
public measureText(text: string, style: TextStyle): { width: number; height: number; scale: number; offsetY: number }
{
return this.getLayout(text, style);
}
/**
* Generates a bitmap-font for the given style and character set
* @param options - Setup options for font generation.
* @returns Font generated by style options.
* @example
* import { BitmapFontManager, BitmapText } from 'pixi.js';
*
* BitmapFontManager.install('TitleFont', {
* fontFamily: 'Arial',
* fontSize: 12,
* strokeThickness: 2,
* fill: 'purple',
* });
*
* const title = new BitmapText({ text: 'This is the title', fontFamily: 'TitleFont' });
*/
public install(options: BitmapFontInstallOptions): BitmapFont;
/** @deprecated since 7.0.0 */
public install(name: string, style?: TextStyle | TextStyleOptions, options?: BitmapFontInstallOptions): BitmapFont;
// eslint-disable-next-line max-len
public install(...args: [string | BitmapFontInstallOptions, (TextStyle | TextStyleOptions)?, BitmapFontInstallOptions?]): BitmapFont
{
let options = args[0] as BitmapFontInstallOptions;
if (typeof options === 'string')
{
options = {
name: options,
style: args[1],
chars: args[2]?.chars,
resolution: args[2]?.resolution,
padding: args[2]?.padding,
skipKerning: args[2]?.skipKerning,
} as BitmapFontInstallOptions;
// #if _DEBUG
// eslint-disable-next-line max-len
deprecation(v8_0_0, 'BitmapFontManager.install(name, style, options) is deprecated, use BitmapFontManager.install({name, style, ...options})');
// #endif
}
const name = options?.name;
if (!name)
{
throw new Error('[BitmapFontManager] Property `name` is required.');
}
options = { ...this.defaultOptions, ...options };
const textStyle = options.style;
const style = textStyle instanceof TextStyle ? textStyle : new TextStyle(textStyle);
const overrideFill = style._fill.fill !== null && style._fill.fill !== undefined;
const font = new DynamicBitmapFont({
style,
overrideFill,
skipKerning: options.skipKerning,
padding: options.padding,
resolution: options.resolution,
overrideSize: false
});
const flatChars = resolveCharacters(options.chars);
font.ensureCharacters(flatChars.join(''));
Cache.set(`${name}-bitmap`, font);
font.once('destroy', () => Cache.remove(`${name}-bitmap`));
return font;
}
/**
* Uninstalls a bitmap font from the cache.
* @param {string} name - The name of the bitmap font to uninstall.
*/
public uninstall(name: string)
{
const cacheKey = `${name}-bitmap`;
const font = Cache.get<BitmapFont>(cacheKey);
if (font)
{
Cache.remove(cacheKey);
font.destroy();
}
}
}
export const BitmapFontManager = new BitmapFontManagerClass();