/**
* Bitmap Font Demo - load a proportional .btfont and compare it to the built-in system font.
* @description Load a proportional .btfont file and draw rainbow, alpha-pulsing, and measured text with it.
*
* Part of the BLIT386 demo series.
* Prerequisites:
* Basics https://demos.blit386.dev/basics
* Fonts https://demos.blit386.dev/fonts
*
* Live version: https://demos.blit386.dev/bitmap-font
*
* Earlier demos use BT.systemPrint() - the built-in 6x14 pixel system font that needs no file.
* This demo shows the ALTERNATIVE: loading a proportional bitmap font from a .btfont file.
*
* A "bitmap font" is a font where every letter is pre-drawn as a small picture
* (a grid of pixels), rather than being drawn from mathematical curves.
* This gives text a crisp, retro look that matches pixel art perfectly.
*
* When would you choose a bitmap font over the system font?
* - You want a proportional font (each letter has its own width, like real typography).
* - You need a specific visual style (blocky, cursive, monospace, etc.).
* - You want fine control over per-character color effects (like the rainbow below).
* - You need to measure text width precisely before drawing it (font.measureText()).
*
* When should you stick with BT.systemPrint()?
* - You just need a quick debug overlay (FPS, position, counters).
* - You don't want the extra complexity of loading a font asset.
* - Startup speed matters more than visual style.
*
* This demo shows how to load a font, draw text in different colors,
* make text that changes color over time (rainbow), text that pulses in opacity (alpha),
* and how to measure how wide a piece of text will be before drawing it.
*
* The font-metadata caption in the bottom-right corner is drawn with the shared demo UI kit
* (src/shared/ui.js), so it looks the same as the info panels in every other demo. The
* showcase lines themselves are hand-drawn on purpose - they ARE the lesson.
*/
import { type BitmapFont = BitmapFont
class BitmapFont
Bitmap font backed by a sprite-sheet texture atlas.
The class is responsible for:
- loading `.btfont` metadata and its referenced texture
- exposing glyph lookup by character or character code
- measuring string widths with a small reusable cache
- providing the underlying
{@link
SpriteSheet
}
used for rendering glyph quadsBitmapFont, function bootstrap(DemoClass: DemoConstructor, options?: BootstrapOptions): Promise<boolean>One-liner bootstrap function for BLIT386 demos.
Handles canvas retrieval and engine initialization. Backend selection
(WebGPU or software fallback) is managed internally by BTAPI.
This function provides a streamlined way to start a demo with sensible defaults
while allowing customization through options.bootstrap, const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT, class Color32Mutable 32-bit RGBA color value with 8-bit channels.Color32, class Vector2iInteger 2D vector for pixel-perfect positioning.
Used for points, sizes, directions, and camera offsets throughout the engine.
The API includes both allocation-free `*To()` / `*InPlace()` variants and
convenience methods that return new vectors.Vector2i } from 'blit386';
// The shared demo UI kit. applyTheme() installs the kit's twelve UI colors high in the
// palette (slots 240-251, far above this demo's slots 1-38), and ui.* draws the small
// "Font Info" panel in the bottom-right corner of the screen.
import { import applyThemeapplyTheme, import uiui, import UI_ANCHORSUI_ANCHORS } from './shared/ui.js';
/** @typedef {import('blit386').IBTDemo} IBTDemo */
/** @typedef {import('blit386').Palette} Palette */
/** @typedef {import('blit386').BitmapFont} BitmapFont */
// The x position where the rainbow text row starts.
// Shared by update() (hue calculation) and renderRainbowText() (glyph drawing) so they stay in sync.
const const RAINBOW_ORIGIN_X: 10RAINBOW_ORIGIN_X = 10;
// Every color used for drawing is stored in a numbered palette slot.
// Index 0 is always transparent. Custom colors start at 1.
// The screen background and the Font Info panel use the shared UI theme instead
// (installed by applyTheme() in init()), so there are no background slots here.
const const C_WHITE: 1C_WHITE = 1; // Pure white: title, special characters, 'A'/'B' labels
const const C_RED_TEXT: 3C_RED_TEXT = 3; // Soft red: "Red Text" sample line
const const C_GREEN_TEXT: 4C_GREEN_TEXT = 4; // Soft green: "Green Text" sample line
const const C_BLUE_TEXT: 5C_BLUE_TEXT = 5; // Soft blue: "Blue Text" sample line
const const C_YELLOW_TEXT: 6C_YELLOW_TEXT = 6; // Yellow: "Yellow Text" sample line
const const C_GRAY_TEXT: 7C_GRAY_TEXT = 7; // Light gray: "Measured Width" text
const const C_ORANGE_LINE: 8C_ORANGE_LINE = 8; // Orange: underline below the measured-width text
// We define the rainbow text string before the slot constants so C_PULSE can be derived from it.
// If you change this string, update() will compute the right number of palette colors automatically.
const const RAINBOW_TEXT: "Rainbow Animation!"RAINBOW_TEXT = 'Rainbow Animation!';
// Dynamic slots: each character in RAINBOW_TEXT gets its own animated color slot.
// We start at C_RAINBOW_BASE and reserve one slot per character.
// update() computes each character's current hue and stores it here.
// render() then reads the slot index - no Color32 math happens during drawing!
const const C_RAINBOW_BASE: 20C_RAINBOW_BASE = 20; // first slot for the rainbow characters
// Dynamic slot: placed immediately after the rainbow slots so it can never overlap them
// even if RAINBOW_TEXT changes length. C_PULSE = C_RAINBOW_BASE + RAINBOW_TEXT.length.
const const C_PULSE: anyC_PULSE = const C_RAINBOW_BASE: 20C_RAINBOW_BASE + const RAINBOW_TEXT: "Rainbow Animation!"RAINBOW_TEXT.length; // single slot for the pulsing-text color
// Left margin for labels and body text (filled in init() for the gap after "A: " / "B: ").
const const LABEL_X: 10LABEL_X = 10;
/**
* Demonstrates bitmap font loading and rendering with various text effects.
* Shows static colors, animated rainbow effects, text measurement, and font metadata.
* Contrast this approach with BT.systemPrint() used in the Fonts demo.
*
* @implements {IBTDemo}
*/
class class DemoDemonstrates bitmap font loading and rendering with various text effects.
Shows static colors, animated rainbow effects, text measurement, and font metadata.
Contrast this approach with BT.systemPrint() used in the Fonts demo.Demo {
// font will hold the loaded bitmap font once it is downloaded.
// It starts as null because nothing is loaded yet.
/** @type {BitmapFont | null} */
Demo.font: BitmapFont | nullfont = null;
// palette holds all the colors this demo uses.
/** @type {Palette | null} */
Demo.palette: Palette | nullpalette = null;
// theme holds the palette slot numbers of the shared UI kit colors, filled in by
// applyTheme() in init(). We use theme.bg to clear the screen so this demo's
// background matches every other demo in the series.
Demo.theme: nulltheme = null;
// animTime is a timer that counts up in seconds.
// We use it to control the speed of color animations.
Demo.animTime: numberanimTime = 0;
// Measured in init() from BT.systemPrintMeasure('M') - built-in system font is 6x14.
Demo.systemCharWidth: numbersystemCharWidth = 6;
Demo.systemLineHeight: numbersystemLineHeight = 14;
// Pixel width of the "A: " prefix so title text lines up after the label.
Demo.labelPrefixWidth: numberlabelPrefixWidth = 18;
/**
* Sets up the color palette and downloads the bitmap font.
* Screen size and FPS use engine defaultConfig() (no configure() in this demo).
* Notice the "await" keyword - we wait here until the font file is fully downloaded.
* The built-in system font (BT.systemPrint) skips this step entirely.
* Returns true when the font has loaded successfully, or false if loading fails.
*
* @returns {Promise<boolean>}
*/
async Demo.init(): Promise<boolean>Sets up the color palette and downloads the bitmap font.
Screen size and FPS use engine defaultConfig() (no configure() in this demo).
Notice the "await" keyword - we wait here until the font file is fully downloaded.
The built-in system font (BT.systemPrint) skips this step entirely.
Returns true when the font has loaded successfully, or false if loading fails.init() {
console.log('[BitmapFontDemo] Initializing...');
// Set up the color palette
// We pick every color before drawing anything, like an artist mixing paint.
this.Demo.palette: Palette | nullpalette = const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.paletteCreate: (size?: number) => PaletteCreates a standalone palette instance.paletteCreate(256);
// Static colors that never change from frame to frame.
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_WHITE: 1C_WHITE, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(255, 255, 255)); // pure white
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_RED_TEXT: 3C_RED_TEXT, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(255, 100, 100)); // soft red
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_GREEN_TEXT: 4C_GREEN_TEXT, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(100, 255, 100)); // soft green
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_BLUE_TEXT: 5C_BLUE_TEXT, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(100, 100, 255)); // soft blue
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_YELLOW_TEXT: 6C_YELLOW_TEXT, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(255, 255, 100)); // yellow
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_GRAY_TEXT: 7C_GRAY_TEXT, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(200, 200, 200)); // light gray
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_ORANGE_LINE: 8C_ORANGE_LINE, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(255, 200, 100)); // orange for underlines
// Pre-fill dynamic rainbow slots with gray so they're not empty on the first frame.
for (let let i: numberi = 0; let i: numberi < const RAINBOW_TEXT: "Rainbow Animation!"RAINBOW_TEXT.length; let i: numberi++) {
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_RAINBOW_BASE: 20C_RAINBOW_BASE + let i: numberi, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(128, 128, 128));
}
// Pre-fill pulse slot.
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_PULSE: anyC_PULSE, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(100, 100, 255));
// Install the shared UI kit colors. They land in palette slots 240-251, well above
// this demo's highest slot (C_PULSE = 38), so the two can never collide. The
// returned map remembers which slot each UI color went to (theme.bg, theme.text, ...).
this.Demo.theme: nulltheme = import applyThemeapplyTheme(this.Demo.palette: Palettepalette);
// Tell the engine to use this palette for all drawing.
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.paletteSet: (palette: Palette) => voidStores the active engine palette.
Use this to swap the **entire palette** (e.g. switch between a day and night
theme). After this call the renderer uploads the new palette uniform on the
next frame.
**Palette-value swap (change what a slot looks like):** mutate the live
{@link
BT.palette
}
in place with `palette.set(slot, newColor)`. The renderer
uploads dirty slots on the next frame; no `paletteSet()` or
{@link
BT.spritesRefresh
}
needed.
**Palette-layout swap (same colors, different slot positions):** build a new
palette with the same colors at new indices, call `paletteSet()`, then call
{@link
BT.spritesRefresh
}
so every sprite sheet re-maps its original RGBA
pixels against the new slot layout.paletteSet(this.Demo.palette: Palettepalette);
// Measure the built-in system font once (same helper as demo fonts).
const const glyphSize: Vector2iglyphSize = const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.systemPrintMeasure: (text: string) => Vector2iMeasures the pixel dimensions of a string rendered with the built-in
system font.systemPrintMeasure('M');
this.Demo.systemCharWidth: numbersystemCharWidth = const glyphSize: Vector2iglyphSize.Vector2i.x: numberHorizontal component (defaults to 0).x;
this.Demo.systemLineHeight: numbersystemLineHeight = const glyphSize: Vector2iglyphSize.Vector2i.y: numberVertical component (defaults to 0).y;
this.Demo.labelPrefixWidth: numberlabelPrefixWidth = const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.systemPrintMeasure: (text: string) => Vector2iMeasures the pixel dimensions of a string rendered with the built-in
system font.systemPrintMeasure('A: ').Vector2i.x: numberHorizontal component (defaults to 0).x;
// Load the font file from the server.
// .btfont is BLIT386's custom font format that includes glyph images.
// This is the step that BT.systemPrint() skips - the system font is built in.
try {
this.Demo.font: BitmapFont | nullfont = await class BitmapFontBitmap font backed by a sprite-sheet texture atlas.
The class is responsible for:
- loading `.btfont` metadata and its referenced texture
- exposing glyph lookup by character or character code
- measuring string widths with a small reusable cache
- providing the underlying
{@link
SpriteSheet
}
used for rendering glyph quadsBitmapFont.BitmapFont.load(url: string): Promise<BitmapFont>Loads a bitmap font from a `.btfont` JSON file.
The font descriptor can reference either an embedded PNG data URI
(`data:image/png;base64,...`) or a texture file path relative to the font JSON file.load('/fonts/PragmataPro14.btfont');
// The font object exposes useful metadata you can read and display.
console.log(`[BitmapFontDemo] Loaded font: ${this.Demo.font: BitmapFont | nullfont.BitmapFont.name: stringFont display name.name}`);
console.log(` Size: ${this.Demo.font: BitmapFont | nullfont.BitmapFont.size: numberOriginal font size in points.size}pt`);
console.log(` Line height: ${this.Demo.font: BitmapFont | nullfont.BitmapFont.lineHeight: numberPixels between baselines for multi-line text.lineHeight}px`);
console.log(` Glyphs: ${this.Demo.font: BitmapFont | nullfont.BitmapFont.glyphCount: numberReturns the total number of glyphs loaded into the font.glyphCount}`);
} catch (function (local var) error: unknownerror) {
console.error('[BitmapFontDemo] Failed to load font:', function (local var) error: unknownerror);
return false;
}
// Tell the font about our palette. Font glyphs are stored as white pixels in the
// font's sprite sheet. indexize() maps those white pixels to palette slot C_WHITE (1).
// After this call, BT.printFont() can recolor the glyphs by shifting the palette index.
this.Demo.font: BitmapFont | nullfont.BitmapFont.getSpriteSheet(): SpriteSheetReturns the sprite sheet that owns the font texture atlas.getSpriteSheet().SpriteSheet.indexize(palette: Palette): voidConverts the sprite sheet's RGBA pixels to palette indices.
Each non-transparent pixel is looked up in the provided palette via exact
color matching. Index 0 is always transparent. The resulting indices are
stored internally; an `r8uint` GPU texture is created lazily on the next
`getTexture()` call.
The original RGBA data is retained so `reindexize()` can re-convert after a
palette swap without reloading the image.indexize(this.Demo.palette: Palettepalette);
console.log('[BitmapFontDemo] Font loaded successfully!');
return true;
}
// Runs at a fixed rate (60 times per second).
// We learned about the demo loop in the Basics demo: https://demos.blit386.dev/basics
// We advance the animation timer AND update dynamic palette colors here.
Demo.update(): voidCalled zero or more times per frame at the fixed timestep declared by
`targetFPS`. The accumulator pattern ensures the target rate is met on
average, but a single frame may invoke this multiple times (catch-up) or
not at all. Update simulation, timers, and input-driven state here.
This is a hot path. Minimize allocations, reuse objects, and prefer
in-place vector operations where possible.
Avoid rendering work here; draw in `render()` instead.update() {
// Move the animation clock forward by one fixed update step in seconds.
this.Demo.animTime: numberanimTime += const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.deltaSeconds: numberFixed-step seconds per update tick.
Equivalent to `1 / BT.targetFPS` when `BT.targetFPS` is finite and positive.
Falls back to `1 / 60` when target FPS is non-finite or non-positive.deltaSeconds;
// Update the pulsing text color
// Math.sin returns a wave that smoothly oscillates between -1 and +1.
// The formula "2 * Math.PI * frequency" converts seconds into radians, which is what
// Math.sin expects. With frequency=3 the wave completes exactly 3 full cycles per second.
// Multiplying by 0.5 and adding 0.5 shifts the output from [-1,1] to [0,1].
const const pulse: numberpulse = Math.sin(2 * Math.PI * 3 * this.Demo.animTime: numberanimTime) * 0.5 + 0.5;
// The alpha channel controls how opaque (visible) the text is.
// At pulse=0 the text is nearly invisible; at pulse=1 it is fully opaque.
// RGB stays fixed at (100, 100, 255) - a medium blue - so only opacity changes.
this.Demo.palette: Palette | nullpalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_PULSE: anyC_PULSE, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(100, 100, 255, Math.floor(const pulse: numberpulse * 255)));
// Update the rainbow text character colors
// We compute hue (color wheel position) for each character based on its x position
// and animTime. The font is always loaded here: the demo loop only starts after
// init() finished successfully, and init() returns false when the font fails.
// We learned about HSL (Hue, Saturation, Lightness) colors in the Colors demo:
// https://demos.blit386.dev/colors
let let charX: numbercharX = const RAINBOW_ORIGIN_X: 10RAINBOW_ORIGIN_X; // Starting x position - same as where render() draws the rainbow text.
for (let let i: numberi = 0; let i: numberi < const RAINBOW_TEXT: "Rainbow Animation!"RAINBOW_TEXT.length; let i: numberi++) {
// hue is a position on the color wheel (0=red, 120=green, 240=blue, 360=back to red).
// Using charX (actual x position) matches the visual rhythm of the rainbow.
// Adding animTime*100 scrolls the rainbow to the left over time.
// The % 360 keeps hue within the 0-359 range so it cycles smoothly around the
// color wheel instead of growing unbounded as animTime increases.
const const hue: numberhue = (let charX: numbercharX * 3 + this.Demo.animTime: numberanimTime * 100) % 360;
this.Demo.palette: Palette | nullpalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_RAINBOW_BASE: 20C_RAINBOW_BASE + let i: numberi, class Color32Mutable 32-bit RGBA color value with 8-bit channels.Color32.Color32.fromHSL(h: number, s: number, l: number, a?: number): Color32Creates a color from HSL values.fromHSL(const hue: numberhue, 100, 60));
// Advance charX by this character's actual pixel width in the font.
// This is specific to BitmapFont - the system font advances a fixed systemCharWidth per character.
const const glyph: Glyph | nullglyph = this.Demo.font: BitmapFont | nullfont.BitmapFont.getGlyph(char: string): Glyph | nullReturns glyph data for a character.
Uses the ASCII lookup table for single-byte characters and falls back to the Unicode glyph
map for everything else, then to the font's fallback glyph (see
{@link
FALLBACK_GLYPH_CHAR
}
)
when neither has an entry for the character.getGlyph(const RAINBOW_TEXT: "Rainbow Animation!"RAINBOW_TEXT[let i: numberi]);
let charX: numbercharX += const glyph: Glyph | nullglyph ? const glyph: Glyphglyph.Glyph.advance: numberHorizontal advance after drawing (distance to next character).advance : 7;
}
}
// Runs once per screen refresh to draw all the text demonstrations on screen.
Demo.render(): voidCalled once per `requestAnimationFrame` tick (browser refresh rate).
Issue all draw calls for the current frame here.
When
{@link
HardwareSettings.isOverlayEnabled
}
is `true` (default), the engine
draws a screen-space overlay HUD after this method returns (present FPS, target FPS, draw calls,
frame/update()/render() timings, backend, demo title). Optional
{@link
overlayRows
}
adds stacked bars above
the footer.
Demos do not need to duplicate engine overlay text. Reserve about ~42 px at the top and space for the bottom palette
grid (or ~13 px when
{@link
HardwareSettings.isOverlayPaletteEnabled
}
is `false`) at the bottom (plus ~14 px per
custom overlay row) for overlay bars, or disable the overlay in `configure()` when using custom full-screen HUD
layouts.
This is a hot path. Batch draws by texture to reduce GPU state changes
and reuse Color32/Vector2i instances instead of allocating per frame.
Avoid mutating the simulation state here unless it is strictly visual.render() {
// Fill the screen with the shared UI theme's deep navy background.
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.clear: (paletteIndex: number) => voidSets the frame clear color using a palette index.
The renderer uses this color when clearing the full display at the start
of the next frame.clear(this.Demo.theme: nulltheme.bg);
// Start drawing from near the top of the screen.
let let y: numbery = 10;
// X where title text starts after the "A: " / "B: " labels (measured in init()).
const const textX: numbertextX = const LABEL_X: 10LABEL_X + this.Demo.labelPrefixWidth: numberlabelPrefixWidth;
// lineHeight tells us how many pixels tall one line of text is.
// Bitmap font line height comes from the .btfont file; system font uses systemLineHeight.
const const bitmapLineHeight: numberbitmapLineHeight = this.Demo.font: BitmapFont | nullfont.BitmapFont.lineHeight: numberPixels between baselines for multi-line text.lineHeight + 2;
const const systemLineHeight: numbersystemLineHeight = this.Demo.systemLineHeight: numbersystemLineHeight + 2;
// BT.printFont() arguments: (font, position, text, colorOffset)
// The colorOffset is a 0-based index FROM palette slot 1.
// So offset 0 = slot 1 (C_WHITE), offset 2 = slot 3 (C_RED_TEXT), etc.
// This is different from BT.systemPrint() which takes the palette slot number directly.
// We learned about palette offset math in demo palette-presets and the palette guides.
// Draw the title in both fonts, one below the other, so you can compare them side-by-side.
// "A:" marks the bitmap font version (proportional spacing, each letter its own width).
// "B:" marks the built-in system font (every character is a fixed 6x14 pixel block).
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.systemPrint: (pos: Vector2i, paletteIndex: number, text: string) => voidDraws text using the built-in 6x14 system font.
The system font covers printable ASCII (characters 32-126). For custom
bitmap fonts with proportional glyphs, use
{@link
BT.printFont
}
instead.systemPrint(new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(const LABEL_X: 10LABEL_X, let y: numbery), const C_WHITE: 1C_WHITE, 'A:');
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.printFont: (font: BitmapFont, pos: Vector2i, text: string, paletteOffset?: number) => voidDraws text with a bitmap font through the indexed sprite pipeline.
Supports proportional glyph widths and glyph-level offsets defined by the
supplied
{@link
BitmapFont
}
. The font's underlying sprite sheet must have
been indexized before calling this.
Palette offset semantics and out-of-range behavior are identical to
{@link
BT.drawSprite
}
.printFont(this.Demo.font: BitmapFont | nullfont, new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(const textX: numbertextX, let y: numbery), 'BLIT386 Bitmap Font Demo', 0);
let y: numbery += const bitmapLineHeight: numberbitmapLineHeight;
// The same title text in the system font so the visual difference is obvious.
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.systemPrint: (pos: Vector2i, paletteIndex: number, text: string) => voidDraws text using the built-in 6x14 system font.
The system font covers printable ASCII (characters 32-126). For custom
bitmap fonts with proportional glyphs, use
{@link
BT.printFont
}
instead.systemPrint(new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(const LABEL_X: 10LABEL_X, let y: numbery), const C_WHITE: 1C_WHITE, 'B:');
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.systemPrint: (pos: Vector2i, paletteIndex: number, text: string) => voidDraws text using the built-in 6x14 system font.
The system font covers printable ASCII (characters 32-126). For custom
bitmap fonts with proportional glyphs, use
{@link
BT.printFont
}
instead.systemPrint(new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(const textX: numbertextX, let y: numbery), const C_WHITE: 1C_WHITE, 'BLIT386 Bitmap Font Demo');
// Move down past both title lines, with a little extra gap before the next section.
let y: numbery += const systemLineHeight: numbersystemLineHeight + 4;
// Draw each section in order, updating y as we go so nothing overlaps.
let y: numbery = this.Demo.renderColoredText(y: any, lineHeight: any): anyrenderColoredText(let y: numbery, const bitmapLineHeight: numberbitmapLineHeight);
let y: numbery = this.Demo.renderRainbowText(y: any, lineHeight: any): anyrenderRainbowText(let y: numbery, const bitmapLineHeight: numberbitmapLineHeight);
let y: numbery = this.Demo.renderPulsingText(y: any, lineHeight: any): anyrenderPulsingText(let y: numbery, const bitmapLineHeight: numberbitmapLineHeight);
let y: numbery = this.Demo.renderSpecialCharacters(y: any, lineHeight: any): anyrenderSpecialCharacters(let y: numbery, const bitmapLineHeight: numberbitmapLineHeight);
this.Demo.renderTextMeasurement(y: any, lineHeight: any): anyrenderTextMeasurement(let y: numbery, const bitmapLineHeight: numberbitmapLineHeight);
// Draw a small panel of font metadata (name, size, glyph count) in the bottom-right
// corner using the shared UI kit. Measured FPS and the demo title are drawn by the
// engine overlay.
this.Demo.renderFontInfo(): voidrenderFontInfo();
}
// Draws the same four words, each in a different color.
// This shows how passing different palette offsets changes the text color.
// Compare to BT.systemPrint() where you pass the palette slot directly.
// y: the Y position to start drawing at.
// lineHeight: how many pixels to move down between lines.
// Returns the Y position after the last line drawn.
Demo.renderColoredText(y: any, lineHeight: any): anyrenderColoredText(y: anyy, lineHeight: anylineHeight) {
// Use a local variable so we don't modify the original parameter.
// In JavaScript, changing a parameter's value inside a function can confuse readers
// because they expect the original value to stay the same throughout the function.
let let currentY: anycurrentY = y: anyy;
// Each color is looked up by offset: palette[1 + offset] = the desired color.
// C_RED_TEXT = 3, so offset = 3 - 1 = 2. That means palette[1 + 2] = palette[3] = red.
// With BT.systemPrint() you would just write: BT.systemPrint(pos, C_RED_TEXT, text).
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.printFont: (font: BitmapFont, pos: Vector2i, text: string, paletteOffset?: number) => voidDraws text with a bitmap font through the indexed sprite pipeline.
Supports proportional glyph widths and glyph-level offsets defined by the
supplied
{@link
BitmapFont
}
. The font's underlying sprite sheet must have
been indexized before calling this.
Palette offset semantics and out-of-range behavior are identical to
{@link
BT.drawSprite
}
.printFont(this.Demo.font: BitmapFont | nullfont, new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(10, let currentY: anycurrentY), 'Red Text', const C_RED_TEXT: 3C_RED_TEXT - 1);
let currentY: anycurrentY += lineHeight: anylineHeight;
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.printFont: (font: BitmapFont, pos: Vector2i, text: string, paletteOffset?: number) => voidDraws text with a bitmap font through the indexed sprite pipeline.
Supports proportional glyph widths and glyph-level offsets defined by the
supplied
{@link
BitmapFont
}
. The font's underlying sprite sheet must have
been indexized before calling this.
Palette offset semantics and out-of-range behavior are identical to
{@link
BT.drawSprite
}
.printFont(this.Demo.font: BitmapFont | nullfont, new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(10, let currentY: anycurrentY), 'Green Text', const C_GREEN_TEXT: 4C_GREEN_TEXT - 1);
let currentY: anycurrentY += lineHeight: anylineHeight;
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.printFont: (font: BitmapFont, pos: Vector2i, text: string, paletteOffset?: number) => voidDraws text with a bitmap font through the indexed sprite pipeline.
Supports proportional glyph widths and glyph-level offsets defined by the
supplied
{@link
BitmapFont
}
. The font's underlying sprite sheet must have
been indexized before calling this.
Palette offset semantics and out-of-range behavior are identical to
{@link
BT.drawSprite
}
.printFont(this.Demo.font: BitmapFont | nullfont, new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(10, let currentY: anycurrentY), 'Blue Text', const C_BLUE_TEXT: 5C_BLUE_TEXT - 1);
let currentY: anycurrentY += lineHeight: anylineHeight;
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.printFont: (font: BitmapFont, pos: Vector2i, text: string, paletteOffset?: number) => voidDraws text with a bitmap font through the indexed sprite pipeline.
Supports proportional glyph widths and glyph-level offsets defined by the
supplied
{@link
BitmapFont
}
. The font's underlying sprite sheet must have
been indexized before calling this.
Palette offset semantics and out-of-range behavior are identical to
{@link
BT.drawSprite
}
.printFont(this.Demo.font: BitmapFont | nullfont, new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(10, let currentY: anycurrentY), 'Yellow Text', const C_YELLOW_TEXT: 6C_YELLOW_TEXT - 1);
// Add extra space after this section.
let currentY: anycurrentY += lineHeight: anylineHeight + 4;
return let currentY: anycurrentY;
}
// Draws text where each character has a different color, and the colors
// shift over time to create a flowing rainbow animation.
// The colors were pre-computed in update() and stored in palette slots C_RAINBOW_BASE+i.
// This technique works with BT.printFont() because it draws one character at a time
// with a different palette offset for each glyph.
// y: the Y position to start drawing at.
// lineHeight: how many pixels to move down between lines.
// Returns the Y position after the text.
Demo.renderRainbowText(y: any, lineHeight: any): anyrenderRainbowText(y: anyy, lineHeight: anylineHeight) {
// Start drawing from the left margin (must match RAINBOW_ORIGIN_X used in update()).
let let x: numberx = const RAINBOW_ORIGIN_X: 10RAINBOW_ORIGIN_X;
let let slotIndex: numberslotIndex = 0;
// Loop through each character in the string one at a time.
for (const const char: anychar of const RAINBOW_TEXT: "Rainbow Animation!"RAINBOW_TEXT) {
// The palette offset for character i = C_RAINBOW_BASE + i - 1.
// This is because printFont offset N means palette[1 + N].
// We want palette[C_RAINBOW_BASE + i], so offset = C_RAINBOW_BASE + i - 1.
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.printFont: (font: BitmapFont, pos: Vector2i, text: string, paletteOffset?: number) => voidDraws text with a bitmap font through the indexed sprite pipeline.
Supports proportional glyph widths and glyph-level offsets defined by the
supplied
{@link
BitmapFont
}
. The font's underlying sprite sheet must have
been indexized before calling this.
Palette offset semantics and out-of-range behavior are identical to
{@link
BT.drawSprite
}
.printFont(this.Demo.font: BitmapFont | nullfont, new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(let x: numberx, y: anyy), const char: anychar, const C_RAINBOW_BASE: 20C_RAINBOW_BASE - 1 + let slotIndex: numberslotIndex);
// Look up how wide this character is in the font's glyph table.
// "advance" is the number of pixels to move right before drawing the next character.
// This is unique to BitmapFont - each letter has its own width in a proportional font.
// The built-in system font always advances systemCharWidth pixels per character (6 by default).
const const glyph: Glyph | nullglyph = this.Demo.font: BitmapFont | nullfont.BitmapFont.getGlyph(char: string): Glyph | nullReturns glyph data for a character.
Uses the ASCII lookup table for single-byte characters and falls back to the Unicode glyph
map for everything else, then to the font's fallback glyph (see
{@link
FALLBACK_GLYPH_CHAR
}
)
when neither has an entry for the character.getGlyph(const char: anychar);
// If the glyph exists, use its advance width; otherwise fall back to 7 pixels.
let x: numberx += const glyph: Glyph | nullglyph ? const glyph: Glyphglyph.Glyph.advance: numberHorizontal advance after drawing (distance to next character).advance : 7;
let slotIndex: numberslotIndex++;
}
return y: anyy + lineHeight: anylineHeight + 4;
}
// Draws text that pulses in opacity - it fades in and out in a smooth rhythm (alpha pulsing).
// The alpha value is pre-computed in update() using Math.sin and stored in palette slot C_PULSE.
// This palette animation technique works exactly the same with BT.systemPrint().
// y: the Y position to start drawing at.
// lineHeight: how many pixels to move down between lines.
// Returns the Y position after the text.
Demo.renderPulsingText(y: any, lineHeight: any): anyrenderPulsingText(y: anyy, lineHeight: anylineHeight) {
// C_PULSE - 1 = 37. That means palette[1 + 37] = palette[38] = C_PULSE (the animated color).
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.printFont: (font: BitmapFont, pos: Vector2i, text: string, paletteOffset?: number) => voidDraws text with a bitmap font through the indexed sprite pipeline.
Supports proportional glyph widths and glyph-level offsets defined by the
supplied
{@link
BitmapFont
}
. The font's underlying sprite sheet must have
been indexized before calling this.
Palette offset semantics and out-of-range behavior are identical to
{@link
BT.drawSprite
}
.printFont(this.Demo.font: BitmapFont | nullfont, new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(10, y: anyy), 'Pulsing Text', const C_PULSE: anyC_PULSE - 1);
return y: anyy + lineHeight: anylineHeight + 4;
}
// Shows that the font can draw special characters like multiplication signs.
// y: the Y position to start drawing at.
// lineHeight: how many pixels to move down between lines.
// Returns the Y position after the text.
Demo.renderSpecialCharacters(y: any, lineHeight: any): anyrenderSpecialCharacters(y: anyy, lineHeight: anylineHeight) {
// Offset 0 = palette[1] = C_WHITE = white text.
// The '\u00D7' is the Unicode multiplication sign (×), which is a non-ASCII character.
// This tests that the font includes glyphs outside the basic Latin alphabet.
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.printFont: (font: BitmapFont, pos: Vector2i, text: string, paletteOffset?: number) => voidDraws text with a bitmap font through the indexed sprite pipeline.
Supports proportional glyph widths and glyph-level offsets defined by the
supplied
{@link
BitmapFont
}
. The font's underlying sprite sheet must have
been indexized before calling this.
Palette offset semantics and out-of-range behavior are identical to
{@link
BT.drawSprite
}
.printFont(this.Demo.font: BitmapFont | nullfont, new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(10, y: anyy), 'Special: 3 \u00D7 4 = 12', 0);
return y: anyy + lineHeight: anylineHeight;
}
// Demonstrates font.measureText() - which tells you exactly how wide a string will
// be before you draw it. We draw an underline that is exactly the right length.
// BT.systemPrint() does not have a measureText() equivalent; this is a BitmapFont-only feature.
// y: the Y position to start drawing at.
// lineHeight: how many pixels to move down between lines.
// Returns the Y position after the text and underline.
Demo.renderTextMeasurement(y: any, lineHeight: any): anyrenderTextMeasurement(y: anyy, lineHeight: anylineHeight) {
const const measureText: "Measured Width"measureText = 'Measured Width';
// Ask the font how many pixels wide this string will be when drawn.
// The system font doesn't support this - it's a BitmapFont-specific feature.
const const textWidth: numbertextWidth = this.Demo.font: BitmapFont | nullfont.BitmapFont.measureText(text: string): numberMeasures the horizontal pixel width of a text string.
Results are cached for repeated measurements. Iterates by Unicode code point (`for...of`),
not by UTF-16 code unit, so an astral character (a surrogate pair) is measured as the single
glyph it is instead of two lone-surrogate lookups. Glyph resolution (including the ASCII
fast path and fallback-glyph substitution) goes through
{@link
getGlyph
}
, so a measured
width always matches what actually renders.measureText(const measureText: "Measured Width"measureText);
// Draw the text in light gray.
// C_GRAY_TEXT - 1 = 6. That means palette[1 + 6] = palette[7] = C_GRAY_TEXT.
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.printFont: (font: BitmapFont, pos: Vector2i, text: string, paletteOffset?: number) => voidDraws text with a bitmap font through the indexed sprite pipeline.
Supports proportional glyph widths and glyph-level offsets defined by the
supplied
{@link
BitmapFont
}
. The font's underlying sprite sheet must have
been indexized before calling this.
Palette offset semantics and out-of-range behavior are identical to
{@link
BT.drawSprite
}
.printFont(this.Demo.font: BitmapFont | nullfont, new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(10, y: anyy), const measureText: "Measured Width"measureText, const C_GRAY_TEXT: 7C_GRAY_TEXT - 1);
// Draw an orange underline that is exactly as wide as the text we measured.
// The underline sits 2 pixels above the bottom of the line.
// BT.drawLine() takes start point, end point, and a palette slot number (not an offset).
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.drawLine: (p0: Vector2i, p1: Vector2i, paletteIndex: number) => voidDraws a pixel-perfect line between two points.
Uses rasterized line drawing without antialiasing.drawLine(
new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(10, y: anyy + lineHeight: anylineHeight - 2),
new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(10 + const textWidth: numbertextWidth, y: anyy + lineHeight: anylineHeight - 2),
const C_ORANGE_LINE: 8C_ORANGE_LINE,
);
return y: anyy + lineHeight: anylineHeight + 4;
}
// Draws a small panel of BitmapFont metadata: the font's name, point size, and how many
// glyphs (letter pictures) it contains. This is demo-specific info that only BitmapFont
// exposes; the built-in system font has no name or glyph count you can print this way.
// The panel is drawn with the shared UI kit: everything between ui.begin() and ui.end()
// stacks into one bordered group that the kit sizes and anchors for us.
Demo.renderFontInfo(): voidrenderFontInfo() {
// Anchor the group to the bottom-right corner of the screen, away from the showcase
// text on the left and the engine overlay's toggle hint in the bottom-left corner.
import uiui.begin(import UI_ANCHORSUI_ANCHORS.BOTTOM_RIGHT);
// Give the group a background, border, and an amber title.
import uiui.panel('Font Info');
// ui.kv() draws an aligned "KEY: value" row - the key dim, the value bright.
import uiui.kv('FONT', this.Demo.font: BitmapFont | nullfont.BitmapFont.name: stringFont display name.name);
import uiui.kv('SIZE', `${this.Demo.font: BitmapFont | nullfont.BitmapFont.size: numberOriginal font size in points.size}pt`);
import uiui.kv('GLYPHS', this.Demo.font: BitmapFont | nullfont.BitmapFont.glyphCount: numberReturns the total number of glyphs loaded into the font.glyphCount);
// end() closes the group: the kit measures the rows, places the panel, and draws it.
import uiui.end();
}
}
// Hand the Demo class to BLIT386 to start the demo loop.
function bootstrap(DemoClass: DemoConstructor, options?: BootstrapOptions): Promise<boolean>One-liner bootstrap function for BLIT386 demos.
Handles canvas retrieval and engine initialization. Backend selection
(WebGPU or software fallback) is managed internally by BTAPI.
This function provides a streamlined way to start a demo with sensible defaults
while allowing customization through options.bootstrap(class DemoDemonstrates bitmap font loading and rendering with various text effects.
Shows static colors, animated rainbow effects, text measurement, and font metadata.
Contrast this approach with BT.systemPrint() used in the Fonts demo.Demo);