Source packs move to assets/packs (untouched), maps to assets/maps, and the palette/sketch leftovers from the 8-bit era go. tools/mkfont.py repacks VEXED's Micro Chat sheet (8-column grid, 8x10 cells) into the CP437 atlas layout the font loader already reads, cropped to 8x9 so the HUD can use an 8 px line height. The CGA atlas is dropped in its favor. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
69 lines
2.5 KiB
Python
69 lines
2.5 KiB
Python
#!/usr/bin/env python3
|
||
"""Repack VEXED's Micro Chat sheet into the game's CP437 atlas layout.
|
||
|
||
Source: assets/fonts/Micro-Chat-All/Micro-Chat.png — an 8-column grid with 1 px
|
||
gray grid lines, 8×10 cells, black glyphs on transparent; the glyph order comes
|
||
from the "Supported Glyphs (PNG Order)" block in Micro-Chat-Info.md.
|
||
|
||
Output: a 32×8-cell atlas (codepoint = index, like the CGA sheet) with white glyphs
|
||
on opaque black, so game/src/font.rs loads it unchanged. Cells are cropped to source
|
||
rows 1..9 (umlaut dots down to descenders): 8×9 px.
|
||
|
||
python3 tools/mkfont.py
|
||
"""
|
||
|
||
from PIL import Image
|
||
|
||
SRC = "assets/fonts/Micro-Chat-All/Micro-Chat.png"
|
||
INFO = "assets/fonts/Micro-Chat-All/Micro-Chat-Info.md"
|
||
OUT = "assets/fonts/micro-chat-8x9.png"
|
||
|
||
CELL_W, CELL_H = 8, 10 # source cell
|
||
STRIDE_X, STRIDE_Y = 9, 11 # cell + grid line
|
||
CROP_TOP, OUT_H = 1, 9 # keep source rows 1..9
|
||
|
||
# Same table as font::cp437 — the accented slots the game may ask for.
|
||
CP437 = {
|
||
'Ç': 128, 'ü': 129, 'é': 130, 'ä': 132, 'à': 133, 'å': 134, 'ç': 135,
|
||
'ë': 137, 'è': 138, 'ï': 139, 'î': 140, 'ì': 141, 'Ä': 142, 'Å': 143,
|
||
'É': 144, 'ö': 148, 'ò': 149, 'û': 150, 'ù': 151, 'Ö': 153, 'Ü': 154,
|
||
'á': 160, 'í': 161, 'ó': 162, 'ú': 163, 'ñ': 164, 'Ñ': 165, 'ß': 225,
|
||
}
|
||
CP437.update({chr(c): c for c in range(32, 127)})
|
||
|
||
|
||
def source_cells():
|
||
block = open(INFO, encoding="utf-8").read()
|
||
block = block.split("## Supported Glyphs (PNG Order)")[1].split("``")[1]
|
||
cells = {}
|
||
for row, line in enumerate(block.strip("\n").split("\n")):
|
||
for col, ch in enumerate(line):
|
||
cells[ch] = (col, row)
|
||
return cells
|
||
|
||
|
||
def main():
|
||
src = Image.open(SRC).convert("RGBA")
|
||
cells = source_cells()
|
||
out = Image.new("RGB", (32 * CELL_W, 8 * OUT_H), (0, 0, 0))
|
||
missing = []
|
||
for ch, code in sorted(CP437.items(), key=lambda kv: kv[1]):
|
||
if ch not in cells:
|
||
missing.append(ch)
|
||
continue
|
||
if ch == " ":
|
||
continue # the sheet marks the space cell with a bar; ours must be blank
|
||
col, row = cells[ch]
|
||
x0, y0 = 1 + col * STRIDE_X, 1 + row * STRIDE_Y + CROP_TOP
|
||
for y in range(OUT_H):
|
||
for x in range(CELL_W):
|
||
r, g, b, a = src.getpixel((x0 + x, y0 + y))
|
||
if a > 0 and r < 100:
|
||
out.putpixel(((code % 32) * CELL_W + x, (code // 32) * OUT_H + y), (255, 255, 255))
|
||
out.save(OUT)
|
||
print(f"wrote {OUT}; missing: {''.join(missing) or '-'}")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|