FNT — 1-бітні растрові шрифти
Джерело: coreprime/kbot @ 0ab97bf · Редагувати на GitHub
У Total Annihilation є цілий набір файлів
.fnt— для всього, від радіоперемовин на екрані (comix.fnt) до екранів брифінгу юнітів (armbrief.fnt). Це крихітні растрові шрифти змінної ширини, 1 біт на піксель, індексовані 8-бітним кодом символу, — по суті аркуш спрайтів із таблицею пошуку.
fonts/comix.fnt, відрендерений командою kbot fnt sheet як аркуш спрайтів у 16 стовпців
kbot fnt render comix.fnt --text "kbot" --fg "#ffff00" --bg "#000000"
Tip
Спробуйте самі.
kbot fnt info fonts/comix.fnt # one-line summary
kbot fnt describe fonts/comix.fnt --list # full metadata + glyph list
kbot fnt sheet fonts/comix.fnt -o sheet.png # all glyphs as a grid
kbot fnt render fonts/comix.fnt --text "Commander" --fg "#ffff00" --bg transparent -o caption.png
kbot fnt dump fonts/comix.fnt -t ./glyphs # one PNG per character
Див. довідник CLI kbot fnt.
З Go. Скористайтеся пакетом formats/fnt:
import "github.com/coreprime/kbot/formats/fnt"
f, _ := os.Open("fonts/comix.fnt")
defer f.Close()
font, _ := fnt.LoadFromReader(f)
fmt.Println(font.Height, "px,", font.GlyphCount(), "glyphs")
if g := font.Glyphs['A']; g != nil {
fmt.Println("A is", g.Width, "px wide")
}
Структура на диску
┌─ 4-byte header ──────────┐
│ uint8 Height │ byte 0: shared by every glyph; the line height
│ uint8 (ignored) │ byte 1: not part of the height
│ int8 Baseline │ byte 2: rows the glyphs extend above the pen
│ uint8 FirstChar │ byte 3: code of the first offset-table entry
├─ (256 − FirstChar) × u16 ┤ file offsets; 0 = glyph not defined
├─ Glyph data ─────────────┤ for each defined glyph:
│ uint8 Width │ pixel width (1..255), also the pen advance
│ ⌈Width × Height/8⌉ │ 1bpp pixels, MSB-first bit stream
└──────────────────────────┘
У кожному релізному шрифті FirstChar = 0, тож його заголовок разом із
таблицею зміщень займає 4 + 256 × 2 = 516 байтів, а дані гліфів
починаються одразу після них.
| Поле | Примітки |
|---|---|
Height |
Лише байт 0. Висота в пікселях, спільна для всіх гліфів шрифту (9–17 у релізних шрифтах). |
Baseline |
Байт 2, знаковий. Текст, намальований із пером у рядку y, розміщує верхній рядок гліфів у y − Baseline. Релізні шрифти зберігають 1–3. |
FirstChar |
Байт 3. Символ c використовує запис таблиці зміщень c − FirstChar; коди, менші за нього, гліфів не мають. Релізні шрифти зберігають 0. |
Offsets[] |
Індексується байтом символу (текст релізної гри — у Windows-1252). 0 означає для цього символу гліфа немає. Ненульові значення — це зміщення від початку шрифту, що вказують на байт Width гліфа. |
Width гліфа |
1–255. Ширина 0 змушує гру зсунути перо на 0 і намалювати сміття; kbot пропускає такі гліфи й повідомляє про них. |
Старіші інструменти читали байти 2–3 як одне непрозоре слово «прапорців»; насправді це два окремі поля.
Читання гліфа
Дані гліфа — це неперервний бітовий потік від старшого біта (MSB
first), упакований зліва направо, а потім згори вниз, тобто розгортка за
рядками. Кількість бітів — Width × Height, а кількість збережених
байтів — ⌈(Width × Height) / 8⌉.
def read_glyph(file, offset, height):
file.seek(offset)
width = file.read(1)[0]
if width == 0 or width > 128:
return None
n_bits = width * height
n_bytes = (n_bits + 7) // 8
bits = file.read(n_bytes)
pixels = []
for i in range(n_bits):
bit = (bits[i // 8] >> (7 - (i % 8))) & 1
pixels.append(bool(bit))
return Glyph(width, height, pixels)
Даних про кернінг немає, і немає ширини зсуву пера, окремої від Width.
Гра розміщує текст так, і kbot fnt render, MCP-інструмент fnt_render
та попередній перегляд тексту в оглядачі ресурсів роблять те саме:
- кожен байт тексту обирає гліф із цим кодом (спершу сконвертуйте
текст UTF-8 у кодову сторінку гри;
kbot fnt render --codepageза замовчуванням використовує Windows-1252); - гліф зсуває перо рівно на свою ширину, без проміжку;
- символ без гліфа нічого не малює й зсуває перо на 0;
- малювання зупиняється на першому NUL або символі нового рядка.
Тож шрифт із 13 рядків із 10-піксельною A і без гліфа пробілу малює
"A A" завширшки 20 пікселів.
Note
Між рядками немає доповнення. Кожен рядок розгортки продовжується
з того місця в тому самому байті, де закінчився попередній. Гліф
завширшки 5 пікселів і заввишки 7 пікселів займає ⌈35/8⌉ = 5 байтів,
а 5 кінцевих бітів ігноруються.
Розібраний приклад — comix.fnt
$ kbot fnt describe fonts/comix.fnt
Height: 14 px
Baseline: 1 (glyph rows start this many rows above the pen)
First char: 0x00
Glyphs: 94 / 256 defined
Glyph width: min=3 max=13 mean=5.9
Ranges: 0x20-0x7D
Як це читати: це шрифт заввишки 14 пікселів, що охоплює друковані
символи ASCII (від пробілу до }). Ширина коливається від 3 (i, l)
до 13 (M, W). 162 невизначені слоти — це здебільшого недруковані
керівні символи; решта — додаткові символи Latin-1, яких гра не
показує.
Таблиця зміщень для перших десяти символів comix.fnt:
char offset width bytes meaning
0x20 0x021C 4 7 space
0x21 0x0223 3 5 '!'
0x22 0x0228 5 9 '"'
0x23 0x0231 6 11 '#'
0x24 0x023C 5 9 '$'
...
Те саме можна вивантажити командою kbot fnt describe --list.
Поради щодо рендерингу
kbot fnt render підтримує:
| Прапорець | Дія |
|---|---|
--text "..." |
Рядок для рендерингу: один рядок; малювання зупиняється на першому символі нового рядка, як у грі. |
--codepage NAME |
Кодова сторінка, у яку конвертується текст UTF-8: cp1252 (за замовчуванням, як у релізній грі), cp1250, cp1251, cp437, cp850, iso-8859-1, iso-8859-15 або raw, щоб передати байти без змін. Символи, для яких немає байта, стають ?. |
--fg #rrggbb[aa] |
Колір переднього плану. За замовчуванням: білий. |
--bg #rrggbb[aa] | transparent |
Тло. За замовчуванням: прозоре. |
--target PATH |
Шлях до вихідного PNG. Інакше — stdout. |
Символ, якого немає у шрифті, нічого не малює й не займає місця, як і в грі.
Підводні камені
Warning
Offsets[c] == 0 означає «гліфа немає», а не «гліф за зміщенням
0». Зміщення 0 потрапляє в середину заголовка й структурно
неможливе — Cavedog використовує це сторожове значення для позначення
невизначеного гліфа. Завжди вважайте нульові зміщення відсутніми.
- Біти в кожному байті йдуть від старшого (MSB first). У сучасних растрових шрифтах поширеніша домовленість — від молодшого (LSB first); не робіть припущень.
- Бітовий потік між рядками неперервний — у кінці кожного рядка розгортки немає доповнення до межі байта.
- Байти 2 і 3 — це базова лінія й код першого символу, а не слово прапорців. Ненульовий код першого символу скорочує таблицю зміщень.
- Символи — це байти, а не кодові точки Unicode.
€— це байт0x80у Windows-1252; якщо взяти кодову точку за модулем 256, буде обрано не той гліф. - Немає метаданих про те, які символи підтримуються. Щоб з'ясувати набір гліфів, доведеться пройти таблицею зміщень.
Типові розміри
| Ресурс | Діапазон, що трапляється в fonts/*.fnt від Cavedog |
|---|---|
| Розмір файлу | 700 Б – 4 КБ |
| Заголовок + таблиця зміщень | завжди 516 байтів |
| Висота гліфа | 9–17 px |
| Ширина гліфа | 3–13 px |
| Визначених гліфів у шрифті | 60–120 (здебільшого лише друковані символи ASCII) |
| Дані одного гліфа | зазвичай 5–30 байтів (1 байт ширини + упаковані біти) |