Toutes les pages de formats ↓
Graphismes et médias

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.

Planche des glyphes de comix.fnt
fonts/comix.fnt rendue en planche de sprites de 16 colonnes par kbot fnt sheet

« kbot » rendu en jaune
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 --codepage utilise 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'octet 0x80 en 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)

Voir aussi

  • PAL — les polices ne portent pas de palette ; le moteur de rendu vous demande de choisir une couleur de premier plan et de fond, ou d'appliquer vous-même la palette de TA.
  • Glossaire — 1 bpp, flux de bits MSB en premier.