5180 fichiers 38.5 Mo 0 téléchargements 199 vues mis à jour il y a 5 heures ui
NativeFont.ts 5.4 Ko Télécharger le fichier
import { NativeTextRenderer, layoutNativeText, layoutNormalText, prepareNativeBundle, swfGlyphHasInk } from './NativeTextRenderer';
import { NativeBundle, NativeFont, NativeRenderOptions, RasterCache } from './NativeTextTypes';

const fontAssets = {
    regular: new URL('../../assets/native-fonts/ubuntu_regular.json', import.meta.url).href,
    bold: new URL('../../assets/native-fonts/ubuntu_bold.json', import.meta.url).href,
    italic: new URL('../../assets/native-fonts/ubuntu_italic.json', import.meta.url).href,
    boldItalic: new URL('../../assets/native-fonts/ubuntu_bold_italic.json', import.meta.url).href,
    condensed: new URL('../../assets/native-fonts/ubuntu_condensed.json', import.meta.url).href,
    volter: new URL('../../assets/native-fonts/volter.json', import.meta.url).href,
    volterBold: new URL('../../assets/native-fonts/volterb.json', import.meta.url).href
};
type FontKey = keyof typeof fontAssets;
interface LoadedFont {
    font: NativeFont;
    renderer: NativeTextRenderer;
    /** Rasterised glyph occurrences of this font (the renderer's keys do not name the font), shared by every label. */
    rasterCache: RasterCache;
}
const loadingFonts = new Map<FontKey, Promise<LoadedFont>>();
const loadedFonts = new Map<FontKey, LoadedFont>();

export interface NativeFontStyle {
    family: 'Ubuntu' | 'UbuntuCondensed' | 'Volter';
    size: number;
    bold?: boolean;
    italic?: boolean;
    color?: number;
    underline?: boolean;
    background?: number;
    etchingColor?: number;
    etchingPosition?: string;
    antiAliasType?: 'advanced' | 'normal';
    thickness?: number;
    sharpness?: number;
    kerning?: boolean;
    /** Pixels between glyphs. Non-zero text is drawn like the official client's canvas fallback (CanvasSpacedText); zero keeps the native glyph renderer. */
    letterSpacing?: number;
}

function fontKeyFor(style: NativeFontStyle): FontKey {
    return style.family === 'Volter'
        ? style.bold
            ? 'volterBold'
            : 'volter'
        : style.family === 'UbuntuCondensed'
          ? 'condensed'
          : style.bold
            ? style.italic
                ? 'boldItalic'
                : 'bold'
            : style.italic
              ? 'italic'
              : 'regular';
}

/** The font when it has already loaded, so a label can be drawn in the same frame it mounts; null until then. */
export function peekNativeFont(style: NativeFontStyle): LoadedFont | null {
    return loadedFonts.get(fontKeyFor(style)) ?? null;
}

export async function loadNativeFont(style: NativeFontStyle): Promise<LoadedFont> {
    const key = fontKeyFor(style);
    if (!loadingFonts.has(key)) {
        const loading = fetch(fontAssets[key]).then(async (response) => {
            if (!response.ok) throw new Error(`Native font ${key}: HTTP ${response.status}`);
            const font = prepareNativeBundle((await response.json()) as NativeBundle);
            const loaded = { font, renderer: new NativeTextRenderer(font), rasterCache: { entries: new Map(), bytes: 0 } };
            loadedFonts.set(key, loaded);
            return loaded;
        });
        loadingFonts.set(key, loading);
        loading.catch(() => loadingFonts.delete(key));
    }
    return loadingFonts.get(key);
}

export function supportsNativeText(font: NativeFont, text: string): boolean {
    for (let index = 0; index < text.length; index++) {
        const glyph = font.swfGlyphs.get(text.charCodeAt(index));
        if (!glyph || (swfGlyphHasInk(font, glyph, text.charCodeAt(index)) && !font.profile.glyphs.has(text.charCodeAt(index)))) return false;
    }
    return true;
}

export function measureNativeText(font: NativeFont, text: string, style: NativeFontStyle): number {
    const layout = style.antiAliasType === 'normal' ? layoutNormalText : layoutNativeText;
    return layout(font, text, style.size, style.kerning ?? true).textWidth;
}

/** v75 uses AIR glyph profiles, DefineFont3 advances and retained premultiplied pixels. */
export function renderNativeText(loaded: LoadedFont, text: string, style: NativeFontStyle) {
    const options: NativeRenderOptions = {
        size: style.size,
        color: style.color ?? 0,
        background: style.background ?? 0xffffff,
        fontStyle: style.italic ? 'italic' : 'normal',
        antiAliasType: style.antiAliasType ?? 'advanced',
        gridFitType: 'pixel',
        thickness: style.thickness ?? 0,
        sharpness: style.sharpness ?? 0,
        kerning: style.kerning ?? true,
        stageQuality: 'high',
        renderingPipeline: 'habbo-retained',
        textDecoration: style.underline ? 'underline' : null,
        etchingColor: style.etchingColor,
        etchingPosition: style.etchingPosition
    };
    const rendered = loaded.renderer.render(text, options);
    const pixels = style.background === undefined ? unpremultiplyPixels(rendered.retainedPixels) : unpremultiplyPixels(rendered.pixels);
    return { ...rendered, pixels };
}

/** Retained text pixels are premultiplied; a canvas takes straight alpha. Returns a copy. */
export function unpremultiplyPixels(premultiplied: ArrayLike<number>): Uint8ClampedArray<ArrayBuffer> {
    const pixels = new Uint8ClampedArray(premultiplied);
    for (let offset = 0; offset < pixels.length; offset += 4) {
        const alpha = pixels[offset + 3];
        if (alpha === 0 || alpha === 255) continue;
        for (let channel = 0; channel < 3; channel++) pixels[offset + channel] = Math.min(255, Math.round((pixels[offset + channel] * 255) / alpha));
    }
    return pixels;
}
15Sujets
1Dépôts
20Membres
2En ligne
Profile
Inscrit
Dernière visite
Messages
0
Votes positifs
0
Votes négatifs
0

Nous utilisons des cookies pour assurer le bon fonctionnement du site et améliorer votre expérience. en savoir plus

Autoriser