FNT: fuentes de mapa de bits de 1 bit
Fuente: coreprime/kbot @ 0ab97bf · Editar en GitHub
Total Annihilation distribuye una buena colección de archivos
.fntpara todo, desde los mensajes de radio en pantalla (comix.fnt) hasta las pantallas de instrucciones de las misiones (armbrief.fnt). Son fuentes de mapa de bits diminutas, de ancho variable, de 1 bit por píxel e indexadas por un código de carácter de 8 bits: en el fondo, una hoja de sprites con una tabla de consulta.
fonts/comix.fnt renderizada como hoja de sprites de 16 columnas con kbot fnt sheet
kbot fnt render comix.fnt --text "kbot" --fg "#ffff00" --bg "#000000"
Tip
Pruébalo tú mismo.
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
Consulta la referencia de kbot fnt de la CLI.
Desde Go. Usa 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")
}
Estructura en disco
┌─ 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
└──────────────────────────┘
Todas las fuentes de la versión comercial tienen FirstChar = 0, así que
su cabecera y su tabla de desplazamientos ocupan 4 + 256 × 2 = 516
bytes, y los datos de los glifos empiezan justo después.
| Campo | Notas |
|---|---|
Height |
Solo el byte 0. Altura en píxeles que comparten todos los glifos de la fuente (de 9 a 17 en las fuentes de la versión comercial). |
Baseline |
Byte 2, con signo. El texto dibujado con la pluma en la fila y coloca la fila superior de los glifos en y − Baseline. Las fuentes de la versión comercial guardan de 1 a 3. |
FirstChar |
Byte 3. El carácter c usa la entrada c − FirstChar de la tabla de desplazamientos; los códigos inferiores no tienen glifo. Las fuentes de la versión comercial guardan 0. |
Offsets[] |
Indexada por el byte del carácter (el texto del juego comercial está en Windows-1252). 0 significa no hay glifo para este carácter. Los valores distintos de cero son desplazamientos desde el principio de la fuente que apuntan al byte Width del glifo. |
Width del glifo |
1–255. Con un ancho de 0, el juego avanza 0 y dibuja basura; kbot se salta esos glifos y lo indica. |
Las herramientas antiguas leían los bytes 2–3 como una única palabra opaca de «flags»; son dos campos distintos.
Leer un glifo
Los datos del glifo son un flujo de bits continuo, con el MSB
primero, empaquetado de izquierda a derecha y después de arriba abajo, es
decir, un barrido por filas. El número de bits es Width × Height y el
número de bytes guardados es ⌈(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)
No hay datos de kerning ni un avance distinto de Width. El juego
compone el texto así, y kbot fnt render, la herramienta MCP
fnt_render y la vista previa de texto del explorador de recursos hacen
lo mismo:
- cada byte del texto selecciona el glifo con ese código (convierte
primero el texto UTF-8 a la página de códigos del juego;
kbot fnt render --codepageusa Windows-1252 por defecto); - un glifo avanza la pluma exactamente su ancho, sin separación;
- un carácter sin glifo no dibuja nada y avanza 0;
- el dibujo se detiene en el primer NUL o salto de línea.
Por tanto, una fuente de 13 filas con una A de 10 píxeles y sin glifo
de espacio dibuja "A A" con 20 píxeles de ancho.
Note
No hay relleno entre filas. Cada línea de barrido continúa en el
mismo byte donde terminó la anterior. Un glifo de 5 píxeles de ancho y
7 de alto usa ⌈35/8⌉ = 5 bytes, y los 5 bits finales se ignoran.
Ejemplo desarrollado: 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
Cómo leerlo: es una fuente de 14 píxeles de alto que cubre el ASCII
imprimible (del espacio a }). Los anchos van de 3 (i, l) a 13 (M,
W). Las 162 posiciones sin definir son casi todas caracteres de control
no imprimibles; el resto son caracteres adicionales de Latin-1 que el
juego no muestra.
La tabla de desplazamientos de los diez primeros caracteres de
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 '$'
...
Puedes volcar lo mismo con kbot fnt describe --list.
Consejos de renderizado
kbot fnt render admite:
| Opción | Efecto |
|---|---|
--text "..." |
La cadena que se renderiza: una línea; el dibujo se detiene en el primer salto de línea, como en el juego. |
--codepage NAME |
Página de códigos a la que se convierte el texto UTF-8: cp1252 (por defecto, la del juego comercial), cp1250, cp1251, cp437, cp850, iso-8859-1, iso-8859-15, o raw para pasar los bytes tal cual. Los caracteres que no tienen byte pasan a ser ?. |
--fg #rrggbb[aa] |
Color del primer plano. Por defecto: blanco. |
--bg #rrggbb[aa] | transparent |
Fondo. Por defecto: transparente. |
--target PATH |
Ruta del PNG de salida. Si no, la salida estándar. |
Un carácter que no está en la fuente no dibuja nada ni ocupa espacio, como en el juego.
Escollos
Warning
Offsets[c] == 0 significa «sin glifo», no «glifo en el
desplazamiento 0». El desplazamiento 0 cae en mitad de la cabecera y
es estructuralmente imposible: Cavedog reutiliza el valor centinela
para indicar sin definir. Trata siempre los desplazamientos cero como
ausentes.
- El orden de los bits es MSB primero dentro de cada byte. En las fuentes de mapa de bits modernas es más habitual LSB primero; no lo des por hecho.
- El flujo de bits es continuo entre filas: no hay relleno hasta el límite de byte al final de cada línea de barrido.
- Los bytes 2 y 3 son la línea base y el código del primer carácter, no una palabra de flags. Un código de primer carácter distinto de cero acorta la tabla de desplazamientos.
- Los caracteres son bytes, no puntos de código Unicode.
€es el byte0x80en Windows-1252; tomar un punto de código módulo 256 elige el glifo equivocado. - No hay metadatos sobre qué caracteres se admiten. Tienes que recorrer la tabla de desplazamientos para descubrir el conjunto de glifos.
Tamaños habituales
| Recurso | Rango observado en los fonts/*.fnt de Cavedog |
|---|---|
| Tamaño del archivo | 700 B – 4 KB |
| Cabecera + tabla de desplazamientos | siempre 516 bytes |
| Altura del glifo | 9–17 px |
| Ancho del glifo | 3–13 px |
| Glifos definidos por fuente | 60–120 (la mayoría solo ASCII imprimible) |
| Datos por glifo | normalmente 5–30 bytes (1 byte de ancho + bits empaquetados) |