# Fonts and lettering in TFX

[Open the font browser](../analysis/fonts/index.html) for all fourteen sets, editable
samples, complete byte tables and TTF / WOFF2 conversions. Each set also has a
PNG character sheet and a CSV table. This catalogue covers the September 1994
CD executable, version 2.05; it does not imply that every earlier build has
identical lettering.

We already had partial captures under presentations and cockpits. This brings
them together with the other writers, decodes their storage directly and checks
the results by executing the original drawing routines.

The browser's magnification uses physical screen pixels: at 1×, each source
pixel occupies one display pixel. Both sample previews and the character grid
compensate for the browser's reported pixel ratio and align to the screen grid,
including after a zoom change or moving between monitors.

## What is there?

| Set | Native storage | Layout and use |
| --- | --- | --- |
| Presentation 0 | Proportional monochrome records | 13-pixel line box; presentation scripts |
| Presentation 1 | 64 columns of seven row masks | Eight-pixel cells, nine-pixel line box |
| Presentation 2 | 64 columns of six row masks | Six-pixel cells and line box |
| Presentation 3 | Proportional monochrome records | 16-pixel line box; drawing uppercases ASCII letters |
| Presentation 4 | Proportional monochrome records | 12-pixel line box; drawing uppercases ASCII letters |
| Presentation 5 | Proportional monochrome records | Seven-pixel line box; drawing uppercases ASCII letters |
| Presentation 6 | Proportional records plus an extended bank | Seven-pixel line box; translated briefing text |
| Presentation 7 | Proportional monochrome records | 18-pixel line box |
| Presentation 8 | Proportional monochrome records | Nine-pixel line box |
| Presentation 9 | Proportional monochrome records | Seven-pixel line box |
| Driver / diagnostics | 64 columns of six row masks | Eight mask columns, nine-pixel advance; diagnostic text and recording marker |
| Widgets / planner | 115 columns of eight row masks | Mixed case, accents and interface symbols, selected by a 256-byte lookup |
| HUD / instruments | 51 monochrome records | 8×6 masks with proportional advances; HUD, MFD and arming captions |
| Cockpit sprite lettering | 45 four-plane indexed sprites | 8×5 images, four-pixel advance; clock, radio and instrument readouts |

The presentation numbers are the original `FONT` style IDs. The descriptive
names of the other exports are ours. Glyph dimensions and advances can differ
from the line box: neither the record width nor the visible ink width alone is
sufficient to reproduce native text layout.

## Encodings and layout quirks

The browser lists **all 256 byte inputs for each writer**, including control
bytes, aliases, blank cells, skipped entries and unavailable glyphs. CP437 labels
identify those bytes; they are not a promise that the displayed image has that
meaning. For example, the HUD question-mark byte draws an A.

Most proportional fonts use a custom 64-entry lookup addressed by `byte & 63`.
A signed lookup value of −1 skips drawing. Some mapped pointers are absent or
outside usable font data, notably in presentation style 0. Those cells are
marked unavailable rather than replaced with invented lettering. A physical
slot view exposes the decoded bank entries separately from their byte aliases.
For proportional fonts this is the set of usable slots reachable through the
writer, including safe addresses shared with adjacent tables.

The fixed presentation and driver writers also mask to six bits, but **any byte
whose low six bits are zero terminates the string**. Thus `@`, byte 0x80 and
byte 0xC0 can stop drawing, just as NUL does. Their raw slot zero is still
visible in the fixed presentation and driver banks.

Presentation styles 3–5 uppercase ASCII a–z when drawing; their width helpers
measure the original byte. Style 1 gives a capital I a four-pixel advance after
the first character, although measurement counts eight. Styles 0, 7, 8 and 9
subtract a final width trim of 2, 2, 1 and 1 pixels respectively. Style 6's
extended width helper excludes the extra drawing gap. The tables retain both
drawing and measured advances where there is a presentation width helper.

Style 6 has a separate pointer and width table for bytes 0x80–0xFF. Its extended
glyphs begin two pixels above the normal origin. There are gaps and unsafe entries:
byte 0xFE reads into a relocated pointer instead of a stable width entry, and
a bounded native drawing probe faults in the monochrome blitter. Its raw file
width byte is zero, but becomes 10 in the harness after relocation. We mark the
metric unavailable instead of presenting that address byte as a font width.

The widget writer starts two pixels above the supplied Y and clamps that start
to the top of the screen. Its advance is the per-byte table value **plus one**.
The HUD handles a literal space separately with a three-pixel advance.

The cockpit sprite writer treats CR and LF as skips, not line breaks. Its shared
lookup can select slots 45–50 even though `pitfont.spr` contains only 45 frames.
Those inputs are unavailable. Indices 1 and 2 form the lettering, index 107 is a
background write, and index 0 is transparent. The browser's source-index colours
are diagnostic colours, not a selected in-game environment palette.

## Native formats and addresses

Offsets below are relative to the reference executable's **code** object at file
`0x70aa4` or **data** object at file `0xedaa4`. Pointer values are relative to the
data object. The browser's individual source offsets are absolute file offsets.
The decoder rejects an executable with a different SHA-256:

```
2cc41a39fa95a2c9fd883c4220d7bdd9cca00c543c89b019eb270763f59d1d9a
```

A monochrome record begins with byte-sized width and height. Each following row
contains `ceil(width / 8)` bytes, most significant bit first. Pointer and width
tables are separate. Proportional presentation styles share the signed map at
`data+0xcc2f`.

| Style | Pointer table | Width table | Drawing gap | Space |
| --- | --- | --- | ---: | ---: |
| 0 | data+0xd23c | data+0xcd5b | 2 | 6 |
| 3 | data+0xcdc0 | data+0xcc6f | 1 | 4 |
| 4 | data+0xce8c | data+0xcca2 | 2 | 6 |
| 5 | data+0xd024 | data+0xccd5 | 1 | 5 |
| 6 | data+0xcf58 | data+0xcd8b | 1 | 3 |
| 6 extended | data+0xd2fc | data+0xd484 | 1 | — |
| 7 | data+0xd08c | data+0xccef | 2 | 17 |
| 8 | data+0xd11c | data+0xcd13 | 1 | 5 |
| 9 | data+0xd1ac | data+0xcd37 | 1 | 5 |

Fixed-font row tables start at `code+0x5bb51` (style 1), `code+0x63f02`
(style 2) and `code+0x509e5` (driver). These store one byte per character per
row, least significant bit first, with a row stride of 64 bytes.

The widget map is at `code+0x6103d`, advances at `code+0x60f49`, and masks at
`code+0x61132` with a row stride of 115. The HUD map, widths and pointers are at
`data+0x7a44`, `data+0x7a84` and `data+0x7ab8` respectively.

`spr/pitfont.spr` starts with a frame count and little-endian 16-bit offsets.
Each frame stores half-width, height, then four VGA planes. For these 8×5 frames,
the byte at `(x % 4) * 10 + y * 2 + x / 4` selects a pixel. The text lookup is
at `data+0x7688`.

## Modern font exports

There are **15 TTF / WOFF2 pairs**: one for each set and an additional Cockpit
Coverage face. These contain square pixel outlines with recovered bearings and
advances. They do not smooth or redesign the original artwork.

One source pixel is 64 font units in a 1,024-unit em. At a font size of **16px**,
one source pixel occupies one screen pixel. Integer multiples give crisp
magnification. A small TrueType instruction program suppresses automatic
hinting that would otherwise move some of the deliberately uneven pixel edges.

The character maps provide three ways to address glyphs:

- Printable bytes have conventional CP437 Unicode aliases for convenient typing.
- `U+E000 + byte` selects the exact drawable native byte mapping.
- `U+E100 + slot` exposes a physical glyph slot. Style 6's extended slots start
  at slot 0x100, so their addresses start at U+E200.

Terminators, skipped inputs and unavailable pointers have no exported glyph.
A `.notdef` marker makes unsupported text visible. The fonts cannot reproduce
string termination, clipping, palette changes or context-dependent I spacing;
these remain responsibilities of a native-style text writer. Browser font
fallback can also make unsupported typed text look different from the native
preview.

The regular Cockpit face contains indices 1 and 2 as monochrome ink. The
Coverage companion includes every nonzero source pixel, including background
writes. Neither is a colour-font reproduction of the original sprites; their
palette indices remain available in the decoded tables.

These are conversions of the game's lettering. They do not grant a new licence
to the original artwork.

## Reproduction and checks

Run `python3 tools/analyse_fonts.py` with Pillow, fontTools and Brotli installed.
It reads the reference executable and extracted sprite bank without modifying
an installation. It writes the catalogue, sheets, tables and portable fonts
under `analysis/fonts/`.

The export check reloads every TTF through FreeType and compares **3,998 glyph
rasters and advances** with the decoded bitmaps, including physical slots and
both cockpit layers. It checks WOFF2 character maps and horizontal metrics too.
Files have fixed timestamps for reproducible output.

`tools/verify_font_analysis.py` uses the existing Unicorn VGA harness in the
adjacent `tfx-cpp/tools` checkout (`--cpp-tools` can select another location).
It executes the original presentation, widget and HUD writers and compares
whole 320×200 frames, including the mask of written pixels: **3,023 character
frames**, with 49 unsafe entries deliberately excluded. All 45 cockpit frames
are independently checked through the native sprite blitter. Proportional width
helpers have 2,047 byte checks across their eight styles; the relocated, unsafe
style-6 byte 0xFE is excluded.

The driver row decoder is covered by the export raster checks; the driver text
writer is not part of this native execution run. Full results are in
[native-checks.json](../analysis/fonts/native-checks.json). The decoder is
[analyse_fonts.py](../tools/analyse_fonts.py); the separate native check is
[verify_font_analysis.py](../tools/verify_font_analysis.py).
