Files
nrfwatch/tools/gen_fonts.py
T
2026-05-01 11:53:56 +02:00

202 lines
6.2 KiB
Python

#!/usr/bin/env python3
"""Generate bitmap digit/colon fonts as C arrays for the e-ink watchface.
Renders digits as stroked outlines onto a pixel grid.
'#' = black (drawn), '.' = white (background).
Bit=0 is black (drawn), bit=1 is white (background) in the output.
"""
DIGIT_W = 24
DIGIT_H = 40
COLON_W = 8
COLON_H = 40
def make_grid(w, h):
return [["." for _ in range(w)] for _ in range(h)]
def fill_rect(grid, x1, y1, x2, y2):
for y in range(max(0, y1), min(len(grid), y2 + 1)):
for x in range(max(0, x1), min(len(grid[0]), x2 + 1)):
grid[y][x] = "#"
def draw_hline(grid, y, x1, x2, thickness=2):
fill_rect(grid, x1, y - thickness // 2, x2, y + thickness // 2)
def draw_vline(grid, x, y1, y2, thickness=2):
fill_rect(grid, x - thickness // 2, y1, x + thickness // 2, y2)
def draw_digit(d):
g = make_grid(DIGIT_W, DIGIT_H)
W = DIGIT_W
H = DIGIT_H
t = 2 # stroke thickness
m = 3 # margin
top = t // 2
bot = H - t // 2 - 1
left = m + t // 2
right = W - m - t // 2 - 1
mid_y = H // 2
if d == 0:
draw_vline(g, left, top + t, bot - t, t)
draw_vline(g, right, top + t, bot - t, t)
draw_hline(g, top, left, right, t)
draw_hline(g, bot, left, right, t)
elif d == 1:
draw_vline(g, W // 2, top, bot, t)
fill_rect(g, left, top, W // 2, top + t)
elif d == 2:
draw_hline(g, top, left, right, t)
draw_vline(g, right, top, mid_y, t)
draw_hline(g, mid_y, left, right, t)
draw_vline(g, left, mid_y, bot, t)
draw_hline(g, bot, left, right, t)
elif d == 3:
draw_hline(g, top, left, right, t)
draw_vline(g, right, top, bot, t)
draw_hline(g, mid_y, left, right, t)
draw_hline(g, bot, left, right, t)
elif d == 4:
draw_vline(g, left, top, mid_y, t)
draw_hline(g, mid_y, left, right, t)
draw_vline(g, right, top, bot, t)
elif d == 5:
draw_hline(g, top, left, right, t)
draw_vline(g, left, top, mid_y, t)
draw_hline(g, mid_y, left, right, t)
draw_vline(g, right, mid_y, bot, t)
draw_hline(g, bot, left, right, t)
elif d == 6:
draw_hline(g, top, left, right, t)
draw_vline(g, left, top, bot, t)
draw_hline(g, mid_y, left, right, t)
draw_vline(g, right, mid_y, bot, t)
draw_hline(g, bot, left, right, t)
elif d == 7:
draw_hline(g, top, left, right, t)
draw_vline(g, right, top, bot, t)
elif d == 8:
draw_hline(g, top, left, right, t)
draw_hline(g, mid_y, left, right, t)
draw_hline(g, bot, left, right, t)
draw_vline(g, left, top, bot, t)
draw_vline(g, right, top, bot, t)
elif d == 9:
draw_hline(g, top, left, right, t)
draw_vline(g, left, top, mid_y, t)
draw_hline(g, mid_y, left, right, t)
draw_vline(g, right, top, bot, t)
draw_hline(g, bot, left, right, t)
return ["".join(row) for row in g]
def draw_colon():
g = make_grid(COLON_W, COLON_H)
cx = COLON_W // 2
t = 2
fill_rect(g, cx - 1, COLON_H // 4 - 1, cx + 1, COLON_H // 4 + 1)
fill_rect(g, cx - 1, 3 * COLON_H // 4 - 1, cx + 1, 3 * COLON_H // 4 + 1)
return ["".join(row) for row in g]
def pattern_to_bytes(pattern, width):
rows = []
for row_str in pattern:
for byte_idx in range(width // 8):
byte_val = 0
for bit in range(8):
col = byte_idx * 8 + bit
if col < len(row_str) and row_str[col] == "#":
byte_val |= 1 << (7 - bit)
rows.append(byte_val)
return rows
def format_c_array(name, data, width):
entry_bytes = width // 8
lines = []
for i in range(0, len(data), entry_bytes):
chunk = data[i : i + entry_bytes]
lines.append("\t" + ", ".join(f"0x{b:02X}" for b in chunk) + ",")
return f"static const uint8_t {name}[] = {{\n" + "\n".join(lines) + "\n};"
def main():
digit_patterns = {str(d): draw_digit(d) for d in range(10)}
colon_pat = draw_colon()
h_lines = [
"#ifndef FONTS_H",
"#define FONTS_H",
"",
"#include <stdint.h>",
"#include <stddef.h>",
"",
f"#define FONT_DIGIT_W {DIGIT_W}",
f"#define FONT_DIGIT_H {DIGIT_H}",
f"#define FONT_DIGIT_BYTES (FONT_DIGIT_W * FONT_DIGIT_H / 8)",
"",
f"#define FONT_COLON_W {COLON_W}",
f"#define FONT_COLON_H {COLON_H}",
f"#define FONT_COLON_BYTES (FONT_COLON_W * FONT_COLON_H / 8)",
"",
"extern const uint8_t font_digits[10][FONT_DIGIT_BYTES];",
"extern const uint8_t font_colon[FONT_COLON_BYTES];",
"",
"#endif",
"",
]
c_lines = [
'#include "fonts.h"',
"",
]
entry_bytes = DIGIT_W // 8
entries = []
for d in range(10):
data = pattern_to_bytes(digit_patterns[str(d)], DIGIT_W)
rows = []
for i in range(0, len(data), entry_bytes):
chunk = data[i : i + entry_bytes]
rows.append("\t\t" + ", ".join(f"0x{b:02X}" for b in chunk) + ",")
entries.append("\t{\n" + "\n".join(rows) + "\n\t},")
total = DIGIT_W * DIGIT_H // 8
c_lines.append(f"const uint8_t font_digits[10][{total}] = {{")
c_lines.extend(entries)
c_lines.append("};")
c_lines.append("")
colon_data = pattern_to_bytes(colon_pat, COLON_W)
colon_total = COLON_W * COLON_H // 8
colon_rows = []
ceb = COLON_W // 8
for i in range(0, len(colon_data), ceb):
chunk = colon_data[i : i + ceb]
colon_rows.append("\t" + ", ".join(f"0x{b:02X}" for b in chunk) + ",")
c_lines.append(f"const uint8_t font_colon[{colon_total}] = {{")
c_lines.extend(colon_rows)
c_lines.append("};")
c_lines.append("")
return "\n".join(h_lines), "\n".join(c_lines)
if __name__ == "__main__":
header, source = main()
with open("src/fonts.h", "w") as f:
f.write(header)
with open("src/fonts.c", "w") as f:
f.write(source)
print(f"Generated fonts.h ({len(header)} bytes) and fonts.c ({len(source)} bytes)")
print(f"Digit size: {DIGIT_W}x{DIGIT_H} = {DIGIT_W * DIGIT_H // 8} bytes each")
print(f"Colon size: {COLON_W}x{COLON_H} = {COLON_W * COLON_H // 8} bytes")