graph TD
A[Your TTF Font] --> B[font2bs.py]
B --> C[PNG Atlas Pages]
B --> D[FDATA Metric Files]
C --> E[EtcTool]
E --> F[KTX Textures]
F --> G[BombSquad + FontMan]
D --> G
style A fill:#4fc3f7
style C fill:#ffb74d
style D fill:#ffb74d
style F fill:#81c784
style G fill:#ba68c8
TTF → BombSquad texture atlases + font metrics in one command
font2bs converts TrueType fonts into multi-page texture atlases and binary font data optimized for the BombSquad game engine. Works seamlessly with the FontMan plugin.
# Install dependencies
pip install pillow
# Basic conversion
python font2bs.py YourFont.ttf
# Custom project with high-res output
python font2bs.py Arial.ttf \
--project-name MyGameFont \
--font-size 48 \
--texture-width 1024 \
--texture-height 1024 \
--num-glyphs 516Output structure:
out/
├── MyGameFont/ # Ready to deploy with FontMan
│ ├── fonts/
│ │ └── fontSmall*.fdata # Binary glyph metrics
│ └── textures/
│ └── fontSmall*.ktx # Compressed texture atlases
└── preview/
└── fontSmall*.png # Uncompressed previews
font2bs handles the entire pipeline automatically:
- Render → Generates character glyphs from your TTF file
- Pack → Arranges glyphs into texture atlases (258 chars per page)
- Calculate → Extracts precise font metrics for each glyph
- Compress → Converts PNG atlases to GPU-optimized KTX textures (via EtcTool)
- Bundle → Organizes everything into a FontMan-ready project structure
Each page contains exactly 258 characters. The tool automatically splits large character sets across multiple pages.
- Python 3.7+
- Pillow library
pip install pillowFor KTX texture compression, you need EtcTool. The script auto-detects it in your current directory or system PATH.
Build from source:
git clone https://github.com/google/etc2comp.git
cd etc2comp
mkdir build && cd build
cmake ..
make
# Move the executable to your PATH or font2bs.py directory
sudo cp EtcTool/EtcTool /usr/local/bin/ # Linux/Mac
# or keep it in the same folder as font2bs.pySkip KTX conversion:
python font2bs.py YourFont.ttf --no-ktx--project-name NAME # Output folder name (default: NewFont)
--description "TEXT" # Adds a __desc__ file to your project--font-size SIZE # Render size in pixels (default: 32)
--texture-width WIDTH # Atlas width (default: 512)
--texture-height HEIGHT # Atlas height (default: 512)
--output-prefix PREFIX # File naming prefix (default: fontSmall)
--num-glyphs COUNT # Total characters to generate (default: 1280)--no-ktx # Skip compression entirely
--ktx-format FORMAT # Texture format (default: RGBA8)
--ktx-effort EFFORT # Compression quality 0-100 (default: 50)
--ktx-mipmaps MIPMAPS # Mipmap levels (default: 11)
--ktx-errormetric METRIC # Error calculation (default: rgba)Generate a high-quality font with 1032 characters (4 pages):
python font2bs.py RobotoMono-Bold.ttf \
--project-name MonospaceUI \
--font-size 64 \
--texture-width 2048 \
--texture-height 2048 \
--output-prefix monoFont \
--num-glyphs 1032 \
--ktx-effort 80 \
--description "High-res monospace font for game UI"Result:
out/
├── MonospaceUI/
│ ├── fonts/
│ │ ├── monoFont0.fdata
│ │ ├── monoFont1.fdata
│ │ ├── monoFont2.fdata
│ │ └── monoFont3.fdata
│ ├── textures/
│ │ ├── monoFont0.ktx
│ │ ├── monoFont1.ktx
│ │ ├── monoFont2.ktx
│ │ └── monoFont3.ktx
│ └── __desc__
└── preview/
├── monoFont0.png
├── monoFont1.png
├── monoFont2.png
└── monoFont3.png
Each .fdata file contains raw binary data (little-endian floats). Each glyph uses 9 floats (36 bytes):
| Float | Name | Description |
|---|---|---|
| 0 | pen_offset_x |
Horizontal pen offset (normalized to font size) |
| 1 | pen_offset_y |
Vertical pen offset (normalized to font size) |
| 2 | advance |
Horizontal advance width (normalized) |
| 3 | x_size |
Glyph width (normalized) |
| 4 | y_size |
Glyph height (normalized) |
| 5 | tex_min_x |
Left UV coordinate [0-1] |
| 6 | tex_min_y |
Bottom UV coordinate [0-1] |
| 7 | tex_max_x |
Right UV coordinate [0-1] |
| 8 | tex_max_y |
Top UV coordinate [0-1] |
Reading in Python:
import struct
with open('fontSmall0.fdata', 'rb') as f:
while chunk := f.read(36): # 9 floats × 4 bytes
glyph = struct.unpack('<9f', chunk)
print(glyph)| Problem | Solution |
|---|---|
| "Texture atlas size too small" | Increase --texture-width and --texture-height |
| Missing characters | Increase --num-glyphs (e.g., 2048 for extended Unicode) |
| Blurry/pixelated text | Increase --font-size (also increase texture dimensions) |
| EtcTool not found | Build EtcTool and add to PATH, or use --no-ktx |
| "Output directory already exists" | Delete/rename the out folder first |
| Corrupted KTX files | Try --ktx-effort 100 or check EtcTool version |
Common --num-glyphs values:
- 258 → Basic ASCII + Latin-1 (1 page)
- 516 → Extended Latin (2 pages)
- 1032 → Includes common symbols (4 pages)
- 2064 → Full Unicode Basic Multilingual Plane coverage (8 pages)
Each page holds 258 characters exactly. The script automatically calculates the required number of pages.
Copyright 2025 - BrotherBoard
This tool is intended for use with the FontMan BombSquad plugin.
Questions? Feedback? Telegram: @BroBordd
- FontMan - BombSquad plugin for loading custom fonts
- EtcTool - Texture compression utility by Google
- Pillow - Python imaging library