aboutsummaryrefslogtreecommitdiffstats
path: root/tools/genfont.py
blob: 2f94881bf80fde7e6d09495934641957c92ffca0 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#!/usr/bin/env python3
# Generate a 4x8 font as RGBDS asm. Glyphs are up to 3px wide (in a 4px cell,
# 1px right gap) and use the full 8px height with a real baseline at row 5:
#   caps/ascenders  rows 1..5     x-height (lowercase)  rows 2..5
#   descenders reach rows 6..7    row 0 = top gap / line spacing
# Output: 96 glyphs (ASCII 32..127), 8 bytes each; each byte = one 4-px row,
# bit3 = leftmost pixel of the cell, bits 3..1 = the glyph, bit0 = gap.
G = {}
def d(ch, top, rows):        # rows: list of 3-char strings ('#'=on)
    G[ch] = (top, rows)

# ---- uppercase (top=1, 5 tall) ----
CAPS = {
 'A':["##.","#.#","###","#.#","#.#"], 'B':["##.","#.#","##.","#.#","##."],
 'C':[".##","#..","#..","#..",".##"], 'D':["##.","#.#","#.#","#.#","##."],
 'E':["###","#..","##.","#..","###"], 'F':["###","#..","##.","#..","#.."],
 'G':[".##","#..","#.#","#.#",".##"], 'H':["#.#","#.#","###","#.#","#.#"],
 'I':["###",".#.",".#.",".#.","###"], 'J':["..#","..#","..#","#.#",".#."],
 'K':["#.#","#.#","##.","#.#","#.#"], 'L':["#..","#..","#..","#..","###"],
 'M':["#.#","###","###","#.#","#.#"], 'N':["#.#","###","###","###","#.#"],
 'O':[".#.","#.#","#.#","#.#",".#."], 'P':["##.","#.#","##.","#..","#.."],
 'Q':[".#.","#.#","#.#",".#.","..#"], 'R':["##.","#.#","##.","#.#","#.#"],
 'S':[".##","#..",".#.","..#","##."], 'T':["###",".#.",".#.",".#.",".#."],
 'U':["#.#","#.#","#.#","#.#",".##"], 'V':["#.#","#.#","#.#",".#.",".#."],
 'W':["#.#","#.#","###","###","#.#"], 'X':["#.#","#.#",".#.","#.#","#.#"],
 'Y':["#.#","#.#",".#.",".#.",".#."], 'Z':["###","..#",".#.","#..","###"],
}
for c,r in CAPS.items(): d(c,1,r)

# ---- digits (top=1, 5 tall) ----
DIG = {
 '0':[".#.","#.#","#.#","#.#",".#."], '1':[".#.","##.",".#.",".#.","###"],
 '2':["##.","..#",".#.","#..","###"], '3':["##.","..#",".#.","..#","##."],
 '4':["#.#","#.#","###","..#","..#"], '5':["###","#..","##.","..#","##."],
 '6':[".##","#..","##.","#.#",".#."], '7':["###","..#",".#.",".#.",".#."],
 '8':[".#.","#.#",".#.","#.#",".#."], '9':[".#.","#.#",".##","..#","##."],
}
for c,r in DIG.items(): d(c,1,r)

# ---- lowercase: x-height rows 2..5, baseline row 5 ----
# plain x-height (top=2, 4 tall)
XH = {
 'a':["##.",".##","#.#",".##"], 'c':[".##","#..","#..",".##"],
 'e':[".#.","#.#","##.",".##"], 'm':["#.#","###","###","#.#"],
 'n':["##.","#.#","#.#","#.#"], 'o':[".#.","#.#","#.#",".#."],
 'r':["#.#","##.","#..","#.."], 's':[".##","##.",".##","##."],
 'u':["#.#","#.#","#.#",".##"], 'v':["#.#","#.#","#.#",".#."],
 'w':["#.#","###","###","#.#"], 'x':["#.#",".#.",".#.","#.#"],
 'z':["###","..#",".#.","###"],
}
for c,r in XH.items(): d(c,2,r)
# ascenders (top=1, 5 tall)
ASC = {
 'b':["#..","#..","##.","#.#","##."], 'd':["..#","..#",".##","#.#",".##"],
 'f':[".##",".#.","###",".#.",".#."], 'h':["#..","#..","##.","#.#","#.#"],
 'k':["#..","#.#","##.","#.#","#.#"], 'l':["##.",".#.",".#.",".#.",".##"],
 't':[".#.","###",".#.",".#.",".##"], 'i':[".#.","...",".#.",".#.",".#."],
}
for c,r in ASC.items(): d(c,1,r)
# descenders (top=2, body 4 + descender -> rows 2..7)
DESC = {
 'g':[".##","#.#",".##","..#","##."], 'p':["##.","#.#","##.","#..","#.."],
 'q':[".##","#.#",".##","..#","..#"], 'y':["#.#","#.#",".##","..#","##."],
 'j':["..#","...","..#","..#","##."],
}
for c,r in DESC.items(): d(c,2,r)

# ---- punctuation ----
P = {
 '.':(4,[".#."]), ',':(4,[".#.","#.."]), ':':(2,[".#.","...",".#."]),
 ';':(2,[".#.","...",".#.","#.."]), '!':(1,[".#.",".#.",".#.","...",".#."]),
 '?':(1,["##.","..#",".#.","...",".#."]), '-':(3,["###"]),
 '+':(2,[".#.","###",".#."]), '=':(2,["###","...","###"]),
 '*':(1,["#.#",".#.","###",".#.","#.#"]), '/':(1,["..#","..#",".#.","#..","#.."]),
 '\\':(1,["#..","#..",".#.","..#","..#"]), '(':(1,["..#",".#.",".#.",".#.","..#"]),
 ')':(1,["#..",".#.",".#.",".#.","#.."]), '[':(1,["##.","#..","#..","#..","##."]),
 ']':(1,[".##","..#","..#","..#",".##"]), '<':(1,["..#",".#.","#..",".#.","..#"]),
 '>':(1,["#..",".#.","..#",".#.","#.."]), '#':(1,["#.#","###","#.#","###","#.#"]),
 '$':(1,[".##","#.#",".#.","#.#","##."]), '%':(1,["#.#","..#",".#.","#..","#.#"]),
 '&':(1,[".#.","#.#",".#.","#.#",".##"]), "'":(1,[".#.",".#."]),
 '"':(1,["#.#","#.#"]), '@':(1,[".#.","#.#","###","#..",".##"]),
 '_':(7,["###"]), '~':(3,[".##","##."]), '^':(1,[".#.","#.#"]),
 '|':(1,[".#.",".#.",".#.",".#.",".#."]), '`':(1,["#..",".#."]),
 '{':(1,["..#",".#.","##.",".#.","..#"]), '}':(1,["#..",".#.",".##",".#.","#.."]),
 ' ':(0,[]),
}
for c,(t,r) in P.items(): d(c,t,r)

def bits(s):
    v=0
    for i,px in enumerate(s[:3]):
        if px=='#': v |= (1 << (3-i))
    return v

def glyph_bytes(top, rows):
    out=[0]*8
    for k,s in enumerate(rows):
        r = top + k
        if 0 <= r < 8: out[r] = bits(s)
    return out

lines=["; auto-generated by tools/genfont.py - 4x8 mixed-case font, 96 glyphs",
       "SECTION \"font\", ROM0", "FontData::"]
missing=[]
for code in range(32,128):
    ch = chr(code)
    if ch in G:
        top,rows = G[ch]
    else:
        top,rows = 1,["###","#.#","#.#","#.#","###"]   # box for undefined
        if code!=127: missing.append(ch)
    b = glyph_bytes(top,rows)
    label = ch if ch not in '\\' else '\\\\'
    lines.append("    db " + ",".join("$%02X"%x for x in b) + "    ; '%s'"%(label if 32<code<127 else ('SP' if code==32 else 'DEL')))
open("/home/user/dev/gbos/src/font.asm","w").write("\n".join(lines)+"\n")
print("wrote src/font.asm; missing glyphs:", "".join(missing) or "(none)")