Newer
Older
rpg / packages / engine / src / ui / PixelText.ts
import { Text, TextStyleOptions } from 'pixi.js';

/**
 * Пиксельный шрифт движка: VT323 (OFL, есть кириллица).
 * Загружается через FontFace API; PixelText — обёртка над Pixi Text
 * с этим шрифтом. Если игры понадобится свой шрифт — меняется источник,
 * обёртка остаётся.
 */

export const PIXEL_FONT_FAMILY = 'VT323';

/** Фолбэк, если шрифт ещё не загрузился или не поддерживается. */
export const PIXEL_FONT_FALLBACK = 'monospace';

let fontLoaded: Promise<void> | null = null;

/**
 * Resolution растеризации текста (пикселей устройства на виртуальный пиксель).
 * Задаётся Renderer'ом при смене масштаба; до этого тексты чуть мягче — не критично.
 */
let textResolution = 1;

/** Задать resolution растеризации PixelText (вызывается движком при ресайзе). */
export function setPixelTextResolution(resolution: number): void {
    textResolution = Math.max(1, Math.round(resolution));
}

/**
 * Загрузить пиксельный шрифт. url указывает на TTF (приложение решает, где он лежит).
 * Повторные вызовы возвращают уже начатую загрузку. Безопасно вызывать до Pixi.
 */
export function ensurePixelFont(url: string, family: string = PIXEL_FONT_FAMILY): Promise<void> {
    if (fontLoaded) return fontLoaded;
    fontLoaded = (async () => {
        try {
            const face = new FontFace(family, `url(${url})`);
            await face.load();
            document.fonts.add(face);
        } catch (err) {
            // Не критично: PixelText останется с фолбэком monospace.
            console.warn(`[engine] пиксельный шрифт не загрузился (${url}):`, err);
        }
    })();
    return fontLoaded;
}

export interface PixelTextOptions {
    text: string;
    /** Размер в виртуальных пикселях (шрифт растровый — целые числа). */
    size: number;
    /** Цвет (числом, 0xRRGGBB). */
    color?: number;
    /** Перенос по ширине (виртуальные пиксели). */
    wordWrapWidth?: number;
    lineHeight?: number;
    align?: 'left' | 'center' | 'right';
    letterSpacing?: number;
    fontFamily?: string;
}

/** Текст пиксельным шрифтом движка. */
export class PixelText extends Text {
    constructor(options: PixelTextOptions) {
        const family = `${options.fontFamily ?? PIXEL_FONT_FAMILY}, ${PIXEL_FONT_FALLBACK}`;
        const style: TextStyleOptions = {
            fontFamily: family,
            fontSize: options.size,
            fill: options.color ?? 0xffffff,
            letterSpacing: options.letterSpacing ?? 0,
            lineHeight: options.lineHeight
        };
        if (options.wordWrapWidth !== undefined) {
            style.wordWrap = true;
            style.wordWrapWidth = options.wordWrapWidth;
            style.breakWords = true;
        }
        if (options.align !== undefined) {
            style.align = options.align;
        }
        // resolution > 1: глифы растеризуются 1:1 с пикселями устройства — резкий текст.
        super({ text: options.text, style, resolution: textResolution });
    }
}