FNT — polices bitmap 1 bit
Source : coreprime/kbot @ 0ab97bf · Modifier sur GitHub
Total Annihilation livre toute une série de fichiers
.fnt, pour tout, des messages radio à l'écran (comix.fnt) aux écrans de briefing des unités (armbrief.fnt). Ce sont de petites polices bitmap à chasse variable, à 1 bit par pixel, indexées par un code de caractère sur 8 bits — en somme, une planche de sprites accompagnée d'une table de correspondance.
fonts/comix.fnt rendue en planche de sprites de 16 colonnes par kbot fnt sheet
kbot fnt render comix.fnt --text "kbot" --fg "#ffff00" --bg "#000000"
Tip
Essayez vous-même.
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
Consultez la référence de kbot fnt de la CLI.
Depuis Go. Utilisez 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")
}
Disposition sur le disque
┌─ 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
└──────────────────────────┘
Toutes les polices d'origine ont FirstChar = 0 : leur en-tête et leur
table des offsets font donc 4 + 256 × 2 = 516 octets, et les données des
glyphes commencent immédiatement après.
| Champ | Remarques |
|---|---|
Height |
Octet 0 uniquement. Hauteur en pixels commune à tous les glyphes de la police (de 9 à 17 dans les polices d'origine). |
Baseline |
Octet 2, signé. Un texte dessiné avec la plume à la ligne y place la ligne supérieure des glyphes en y − Baseline. Les polices d'origine stockent 1–3. |
FirstChar |
Octet 3. Le caractère c utilise l'entrée c − FirstChar de la table des offsets ; les codes inférieurs n'ont pas de glyphe. Les polices d'origine stockent 0. |
Offsets[] |
Indexé par l'octet du caractère (le texte du jeu d'origine est en Windows-1252). 0 signifie pas de glyphe pour ce caractère. Les valeurs non nulles sont des offsets depuis le début de la police qui pointent sur l'octet Width du glyphe. |
Width du glyphe |
1–255. Avec une largeur de 0, le jeu avance de 0 et dessine n'importe quoi ; kbot ignore ces glyphes et les signale. |
D'anciens outils lisent les octets 2–3 comme un seul mot de « flags » opaque ; ce sont deux champs distincts.
Lire un glyphe
La charge utile d'un glyphe est un flux de bits continu, en commençant
par le bit de poids fort (MSB), rangé de gauche à droite puis de haut en bas
— autrement dit un balayage ligne par ligne. Le nombre de bits est
Width × Height, et le nombre d'octets stockés ⌈(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)
Il n'y a pas de données de crénage, ni de chasse distincte de Width. Le
jeu dispose le texte de la façon suivante, et kbot fnt render, l'outil MCP
fnt_render et l'aperçu de texte de l'explorateur de ressources font de
même :
- chaque octet du texte choisit le glyphe de ce code (convertissez
d'abord le texte UTF-8 dans la page de code du jeu ;
kbot fnt render --codepageutilise Windows-1252 par défaut) ; - un glyphe fait avancer la plume d'exactement sa largeur, sans espacement ;
- un caractère sans glyphe ne dessine rien et avance de 0 ;
- le dessin s'arrête au premier NUL ou saut de ligne.
Une police de 13 lignes avec un A de 10 pixels et sans glyphe d'espace
dessine donc "A A" sur 20 pixels de large.
Note
Il n'y a pas de remplissage entre les lignes. Chaque ligne de balayage
reprend là où la précédente s'est arrêtée, dans le même octet. Un glyphe
de 5 pixels de large et 7 de haut utilise ⌈35/8⌉ = 5 octets, les 5 bits
finaux étant ignorés.
Exemple commenté — 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
Lecture : c'est une police de 14 pixels de haut qui couvre l'ASCII
imprimable (de l'espace à }). Les largeurs vont de 3 (i, l) à 13
(M, W). Les 162 emplacements non définis sont pour la plupart des
caractères de contrôle non imprimables ; les autres sont des caractères
supplémentaires du Latin-1 que le jeu n'affiche pas.
La table des offsets pour les dix premiers caractères 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 '$'
...
Vous pouvez obtenir la même chose avec kbot fnt describe --list.
Conseils de rendu
kbot fnt render prend en charge :
| Option | Effet |
|---|---|
--text "..." |
La chaîne à rendre : une seule ligne ; le dessin s'arrête au premier saut de ligne, comme dans le jeu. |
--codepage NAME |
Page de code vers laquelle le texte UTF-8 est converti : cp1252 (par défaut, celle du jeu d'origine), cp1250, cp1251, cp437, cp850, iso-8859-1, iso-8859-15, ou raw pour transmettre les octets tels quels. Les caractères sans octet correspondant deviennent ?. |
--fg #rrggbb[aa] |
Couleur de premier plan. Par défaut : blanc. |
--bg #rrggbb[aa] | transparent |
Fond. Par défaut : transparent. |
--target PATH |
Chemin du PNG de sortie. Sinon, la sortie standard. |
Un caractère absent de la police ne dessine rien et n'occupe aucune place, comme dans le jeu.
Pièges
Warning
Offsets[c] == 0 signifie « pas de glyphe », pas
« glyphe à l'offset 0 ». L'offset 0 tombe au milieu de l'en-tête et
est structurellement impossible : Cavedog réutilise cette sentinelle pour
signifier non défini. Traitez toujours les offsets nuls comme des glyphes
manquants.
- L'ordre des bits commence par le bit de poids fort (MSB) dans chaque octet. Le bit de poids faible en premier est la convention la plus courante dans les polices bitmap modernes ; ne présumez de rien.
- Le flux de bits est continu d'une ligne à l'autre : aucun remplissage jusqu'à une frontière d'octet à la fin de chaque ligne de balayage.
- Les octets 2 et 3 sont la ligne de base et le code du premier caractère, pas un mot de flags. Un code de premier caractère non nul raccourcit la table des offsets.
- Les caractères sont des octets, pas des points de code Unicode.
€est l'octet0x80en Windows-1252 ; prendre un point de code modulo 256 choisit le mauvais glyphe. - Aucune métadonnée n'indique les caractères pris en charge. Il faut parcourir la table des offsets pour découvrir le jeu de glyphes.
Tailles typiques
| Ressource | Plage observée dans les fonts/*.fnt de Cavedog |
|---|---|
| Taille du fichier | 700 o – 4 Ko |
| En-tête + table des offsets | toujours 516 octets |
| Hauteur des glyphes | 9–17 px |
| Largeur des glyphes | 3–13 px |
| Glyphes définis par police | 60–120 (pour la plupart de l'ASCII imprimable seulement) |
| Données par glyphe | en général 5–30 octets (1 octet de largeur + bits compactés) |