#webgl #gpu #rendering

bin+lib beamterm-atlas

Font atlas generator for beamterm WebGL terminal renderer, creating GPU-optimized texture arrays from TTF/OTF fonts

9 breaking releases

Uses new Rust 2024

new 0.10.0 Dec 25, 2025
0.8.0 Oct 9, 2025
0.5.0 Jun 29, 2025

#351 in Web programming

MIT license

665KB
2.5K SLoC

beamterm-atlas

A font atlas generator for WebGL terminal renderers, optimized for GPU texture memory and rendering efficiency.

Overview

beamterm-atlas generates tightly-packed 2D texture array atlases from TTF/OTF font files, producing a binary format optimized for GPU upload. The system supports multiple font styles, full Unicode including emoji, and automatic grapheme clustering.

Architecture

The crate consists of:

  • Font rasterization engine using cosmic-text for high-quality text rendering
  • 2D texture array packer organizing glyphs into 1×32 grids per texture layer
  • Binary serializer with zlib compression for efficient storage
  • Atlas verification tool for debugging and visualization

Glyph ID Assignment System

ID Structure

The system uses a 16-bit glyph ID that encodes both the base character and its style variations:

Bit Range Purpose Description
0-9 Base Glyph ID 1024 possible base glyphs (0x000-0x3FF)
10 Bold Flag Selects bold variant (0x0400)
11 Italic Flag Selects italic variant (0x0800)
12 Emoji Flag Indicates emoji glyph (0x1000)
13 Underline Underline effect (0x2000)
14 Strikethrough Strikethrough effect (0x4000)
15 Reserved Reserved for future use

The atlas only encodes glyphs with the first 13 bits. Bits 13 and 14 are applied at runtime for text decoration effects, while bit 15 is reserved for future extensions.

Font Style Encoding

Each base glyph automatically generates four style variants by combining the bold and italic flags:

Style Bit Pattern ID Offset Example ('A' = 0x41)
Normal 0x0000 +0 0x0041
Bold 0x0400 +1024 0x0441
Italic 0x0800 +2048 0x0841
Bold+Italic 0x0C00 +3072 0x0C41

This encoding allows the shader to compute texture coordinates directly from the glyph ID without lookup tables.

Character Category Assignment

The generator assigns IDs based on four character categories:

1. ASCII Characters (0x20-0x7E)

  • Printable ASCII only (space through tilde)
  • Direct mapping: character code = base glyph ID
  • Guarantees fast lookup for common characters

2. Halfwidth Unicode Characters

  • Fill unused slots in the 0x00-0x1FF range
  • Sequential assignment starting from first available ID
  • Single-cell width (standard terminal characters)

3. Fullwidth Unicode Characters (Double-Width)

  • Assigned after halfwidth characters, aligned to even cell offsets
  • Each fullwidth glyph occupies TWO consecutive IDs (left half + right half)
  • All four style variants supported (Normal, Bold, Italic, BoldItalic)
  • Used for CJK characters and other wide Unicode glyphs
  • Detected automatically via unicode-width crate

4. Emoji Characters (Double-Width)

  • Start at ID 0x1000 (bit 12 set)
  • Each emoji occupies TWO consecutive IDs (left half + right half)
  • Sequential assignment: 0x1000/0x1001 (emoji 0), 0x1002/0x1003 (emoji 1), etc.
  • No style variants (emoji are always rendered as-is)
  • Maximum 2048 emoji (using 4096 glyph slots)

Texture Layer Allocation

Each font style reserves exactly 32 layers (1024 glyph slots), regardless of actual usage:

Style Range Layers Glyph ID Range Capacity
Normal 0-31 0x0000-0x03FF 1024 slots
Bold 32-63 0x0400-0x07FF 1024 slots
Italic 64-95 0x0800-0x0BFF 1024 slots
BoldItalic 96-127 0x0C00-0x0FFF 1024 slots
Emoji 128+ 0x1000+ Up to 4096

Total: 128 layers for font styles (always allocated) + up to 128 layers for emoji

Example with 500 base glyphs + 100 emoji:

  • Font styles: 128 layers (with gaps - only ~500/1024 slots used per style)
  • Emoji: 7 layers (100 emoji × 2 IDs = 200 slots)
  • Total: 135 layers

Maximum capacity (1024 base glyphs + 2048 emoji):

  • Font styles: 128 layers (fully utilized)
  • Emoji: 128 layers (2048 emoji × 2 IDs = 4096 slots)
  • Total: 256 layers

2D Texture Array Organization

Layer Layout

Each texture layer contains a 1×32 grid of glyphs:

Position in layer = ID & 0x1F (modulo 32)
Grid X = 0 (always single column)
Grid Y = Position (0-31)
Layer = ID ÷ 32

Memory Layout

The 2D texture array uses RGBA format with dimensions:

  • Width: cell_width × 1
  • Height: cell_height × 32
  • Layers: max_glyph_id ÷ 32

The RGBA format is required for emoji support - while monochrome glyphs could use a single channel, emoji glyphs need full color information.

This layout ensures:

  • Efficient GPU memory alignment
  • Cache-friendly access pattern (sequential glyphs in same column)
  • Simple coordinate calculation using bit operations

Rasterization Process

Cell Dimension Calculation

The system determines cell size by measuring the full block character to ensure all glyphs fit within the cell boundaries. Additional padding of 1px on all sides prevents texture bleeding.

Font Style Handling

Each glyph is rendered four times, one for each of the styles (normal, bold, italic, bold+italic).

Double-Width Glyph Handling (Fullwidth & Emoji)

Fullwidth glyphs (e.g., CJK characters) and emoji require special processing as they span two cells:

  1. Rendered at 2× cell width
  2. Split into left and right halves
  3. Each half placed in consecutive glyph slots (even ID = left, odd ID = right)
  4. Fullwidth glyphs are aligned to even cell offsets for efficient texture packing

Fullwidth glyphs:

  • Detected via unicode-width crate (characters with display width of 2)
  • Support all four font style variants
  • IDs assigned sequentially after halfwidth glyphs

Emoji glyphs:

  • IDs start at 0x1000 (EMOJI_FLAG set)
  • No style variants (always rendered as-is)
  • Color information preserved in texture

The presence of emoji is the primary reason the atlas uses RGBA format instead of a single-channel texture. While monochrome glyphs only need an alpha channel, emoji require full color information to render correctly.

Binary Atlas Format

File Structure

The atlas uses a versioned binary format with header validation:

Header (5 bytes)
├─ Magic: [0xBA, 0xB1, 0xF0, 0xA7]
└─ Version: 0x03

Metadata Section
├─ Font name (u8 length + UTF-8 string)
├─ Font size (f32)
├─ Halfwidth glyphs per layer (u32) - boundary between halfwidth and fullwidth glyphs
├─ Texture width (i32)
├─ Texture height (i32)
├─ Texture layers (i32)
├─ Cell width (i32)
├─ Cell height (i32)
├─ Underline position (f32)
├─ Underline thickness (f32)
├─ Strikethrough position (f32)
├─ Strikethrough thickness (f32)
└─ Glyph count (u16)

Glyph Definitions
└─ Per glyph:
   ├─ ID (u16 - includes style bits)
   ├─ Style (u8) - ordinal: 0=Normal, 1=Bold, 2=Italic, 3=BoldItalic
   ├─ Is emoji (u8) - 0=false, 1=true
   ├─ Pixel X (i32)
   ├─ Pixel Y (i32)
   └─ Symbol (u8 length + UTF-8 string)

Compressed Texture Data
├─ Data length (u32)
└─ zlib-compressed RGBA data

Serialization Properties

  • Endianness: Little-endian for cross-platform compatibility
  • Compression: zlib level 9 (typically 75% size reduction)
  • String encoding: Length-prefixed UTF-8 (u8 for strings, max 255 bytes)
  • Texture data: Length-prefixed compressed data (u32 length)
  • Alignment: Natural alignment without padding

Usage

Installation

cargo install beamterm-atlas

Command-Line Interface

beamterm-atlas [OPTIONS] <FONT>

Arguments

  • <FONT> - Font selection by name (partial match) or 1-based index (required unless --list-fonts is used)

Options

  • --emoji-font <FONT> - Emoji font family name to use for emoji glyphs (default: "Noto Color Emoji")
  • --symbols-file <PATH> - File containing symbols (including emoji) to include in the atlas (optional if ranges cover all needed symbols)
  • -r, --range <RANGE> - Unicode ranges in hex format (e.g., 0x2580..0x259F). ASCII (0x20-0x7F) is always included. Can be specified multiple times.
  • -s, --font-size <SIZE> - Font size in points (default: 15.0)
  • -l, --line-height <MULTIPLIER> - Line height multiplier (default: 1.0)
  • -o, --output <PATH> - Output file path (default: "./bitmap_font.atlas")
  • --underline-position <FRACTION> - Underline position from 0.0 (top) to 1.0 (bottom) of cell (default: 0.85)
  • --underline-thickness <PERCENT> - Underline thickness as percentage of cell height (default: 5.0)
  • --strikethrough-position <FRACTION> - Strikethrough position from 0.0 (top) to 1.0 (bottom) of cell (default: 0.5)
  • --strikethrough-thickness <PERCENT> - Strikethrough thickness as percentage of cell height (default: 5.0)
  • --check-missing - Check for missing glyphs and show detailed coverage report
  • -L, --list-fonts - List available fonts and exit

Examples

List all available monospace fonts with complete style variants:

beamterm-atlas --list-fonts

Generate an atlas using JetBrains Mono at 16pt with default Unicode ranges and emoji:

beamterm-atlas "JetBrains Mono" -s 16 -o jetbrains-16.atlas

Generate with custom symbols file (including emoji):

beamterm-atlas "JetBrains Mono" --symbols-file symbols.txt -s 16

Generate with custom Unicode ranges (no symbols file needed):

beamterm-atlas "Hack" \
  --range 0x2500..0x257F \
  --range 0x2580..0x259F \
  -o hack.atlas

Generate with custom emoji font:

beamterm-atlas "Ubuntu Mono" \
  --emoji-font "Noto Color Emoji" \
  --symbols-file symbols.txt

Generate with custom text decoration settings:

beamterm-atlas "Fira Code" \
  --symbols-file symbols.txt \
  --underline-position 0.9 \
  --underline-thickness 7.5 \
  --strikethrough-position 0.45

Check glyph coverage for a font:

beamterm-atlas "Cascadia Code" \
  --symbols-file symbols.txt \
  --check-missing

Select font by index (useful for scripting):

# First, list fonts to see indices
beamterm-atlas -L

# Then select by number
beamterm-atlas 5 --symbols-file symbols.txt -s 14

Character Set

The tool generates an atlas from configurable character sets:

ASCII Range (Always Included)

  • Printable ASCII: 0x20-0x7E (space through tilde)
  • Direct mapping: character code = base glyph ID

Default Unicode Ranges (When no --range specified)

  • Latin-1 Supplement: 0x00A0-0x00FF
  • Latin Extended-A: 0x0100-0x017F
  • Miscellaneous Technical: 0x2300-0x232F, 0x2350-0x23FF
  • Box Drawing: 0x2500-0x257F
  • Block Elements: 0x2580-0x259F
  • Geometric Shapes: 0x25A0-0x25CF, 0x25E2-0x25FF
  • Braille Patterns: 0x2800-0x28FF

Custom Ranges

  • Specify additional ranges with --range 0x{START}..0x{END}
  • Can be specified multiple times
  • Automatically excludes ASCII control characters

Emoji

  • Loaded from symbols file or detected in Unicode ranges
  • Uses separate emoji font (default: "Noto Color Emoji")
  • Double-width rendering (occupies two glyph slots)
  • Maximum 2048 emoji supported

Verification

The verify-atlas binary visualizes the texture layout, showing:

  • Layer organization
  • Character placement
  • Grid boundaries
  • Glyph distribution
verify-atlas <path/to/font.atlas>

# Example: Verify the default atlas
verify-atlas beamterm-data/atlas/bitmap_font.atlas

# Example: Verify a custom atlas
verify-atlas ./my_custom_font.atlas

Font Requirements

The generator requires monospace fonts with all four style variants:

  • Regular
  • Bold
  • Italic
  • Bold+Italic

Fonts missing any variant will not appear in the font list. The system automatically discovers all installed system fonts that meet these requirements.

Dependencies

~31–46MB
~573K SLoC