202 lines
6.2 KiB
Python
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")
|