diff --git a/util/font/generate_font.py b/util/font/generate_font.py index 4af3ed0434..b301a2e533 100644 --- a/util/font/generate_font.py +++ b/util/font/generate_font.py @@ -9,31 +9,40 @@ The output is a C source file containing: 3. A character width table for proportional spacing. Usage: - python generate_font.py > font_table.c + python generate_font.py --width 24 --height 32 > font_table.c """ - +import argparse from PIL import Image, ImageFont, ImageDraw import sys import os -# FONT DIMENSIONS -FONT_SIZE = 19 -CANVAS_WIDTH = 16 -CANVAS_HEIGHT = 24 -Y_OFFSET = -2 - # PRINTABLE ASCII RANGE START_CHAR = 32 END_CHAR = 126 NUM_CHARS = END_CHAR - START_CHAR + 1 -def generate_c_table(font_path): +def generate_c_table(font_path, canvas_width, canvas_height): if not os.path.exists(font_path): print(f"Error: Font file '{font_path}' not found.", file=sys.stderr) sys.exit(1) + # Validation: Width must not exceed 32 bits for uint32_t storage + if canvas_width > 32: + print(f"Error: Requested width ({canvas_width}) exceeds 32-bit capacity. " + "The current renderer supports up to 32 pixels wide.", file=sys.stderr) + sys.exit(1) + + # Validation: Basic sanity check for positive dimensions + if canvas_width <= 0 or canvas_height <= 0: + print("Error: Width and Height must be positive integers.", file=sys.stderr) + sys.exit(1) + + # Use canvas_width as font size to maintain original calculation style + font_size = canvas_width + y_offset = 0 + try: - font = ImageFont.truetype(font_path, FONT_SIZE) + font = ImageFont.truetype(font_path, font_size) except OSError: print(f"Error: Could not open font file '{font_path}'.", file=sys.stderr) sys.exit(1) @@ -43,63 +52,79 @@ def generate_c_table(font_path): print("/*") print(" * THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT EDIT.") print(f" * Source: {os.path.basename(font_path)}") - print(f" * Command: python util/font/{os.path.basename(__file__)} {font_path}") + print(f" * Command: python {os.path.basename(__file__)} {font_path} --width {canvas_width} --height {canvas_height}") print(" */\n") print("#include \n") # 1. Output the Macros for C code usage - print(f"#define FONT_WIDTH {CANVAS_WIDTH}") - print(f"#define FONT_HEIGHT {CANVAS_HEIGHT}") + print(f"#define FONT_WIDTH {canvas_width}") + print(f"#define FONT_HEIGHT {canvas_height}") print(f"#define FONT_START_CHAR {START_CHAR}") print(f"#define FONT_END_CHAR {END_CHAR}") print(f"#define FONT_NUM_CHARS (FONT_END_CHAR - FONT_START_CHAR + 1)\n") widths = [] - # 2. Generate packed font table with block formatting - print(f"const uint16_t font_table[FONT_NUM_CHARS][FONT_HEIGHT] = {{") + # Choose data type based on width + data_type = "uint32_t" + hex_format = "08x" if canvas_width > 16 else "04x" + bit_depth = 32 if canvas_width > 16 else 16 + + # 2. Generate packed font table + print(f"const {data_type} font_table[FONT_NUM_CHARS][FONT_HEIGHT] = {{") for char_code in range(START_CHAR, END_CHAR + 1): char = chr(char_code) - image = Image.new("1", (CANVAS_WIDTH, CANVAS_HEIGHT), 0) + image = Image.new("1", (canvas_width, canvas_height), 0) draw = ImageDraw.Draw(image) - draw.text((0, Y_OFFSET), char, font=font, fill=1) + draw.text((0, y_offset), char, font=font, fill=1) pixels = list(image.getdata()) rows = [] global_mask = 0 - for y in range(CANVAS_HEIGHT): + for y in range(canvas_height): row_val = 0 - base = y * CANVAS_WIDTH - for x in range(CANVAS_WIDTH): + base = y * canvas_width + for x in range(canvas_width): if pixels[base + x]: - row_val |= (1 << ((CANVAS_WIDTH - 1) - x)) + # Map pixels to bits: Leftmost pixel is highest bit + row_val |= (1 << ((canvas_width - 1) - x)) rows.append(row_val) global_mask |= row_val # Dead space removal / Normalization left_shift = 0 actual_width = 0 - if global_mask > 0: - temp_mask = global_mask - while not (temp_mask & 0x8000): - temp_mask <<= 1 - left_shift += 1 - rightmost_bit = 0 - for i in range(CANVAS_WIDTH): - if (global_mask >> i) & 1: - rightmost_bit = i + if global_mask > 0: + # Find the leftmost pixel column + leftmost_col = 0 + for i in range(canvas_width): + if (global_mask >> (canvas_width - 1 - i)) & 1: + leftmost_col = i break - actual_width = ((CANVAS_WIDTH - 1) - left_shift) - rightmost_bit + 1 + + # Find the rightmost pixel column + rightmost_col = 0 + for i in range(canvas_width): + if (global_mask >> i) & 1: + rightmost_col = (canvas_width - 1) - i + break + + # Width is the horizontal span of active pixels + actual_width = rightmost_col - leftmost_col + 1 + + # left_shift ensures character is left-aligned to MSB of the data type + left_shift = (bit_depth - canvas_width) + leftmost_col else: - actual_width = 6 # Default space width + actual_width = canvas_width // 3 # Default width for space widths.append(actual_width) - # Format the pre-shifted hex rows into blocks of 8 - hex_values = [f"0x{(row << left_shift) & 0xFFFF:04x}" for row in rows] + # Pre-shift values to be MSB-aligned and mask based on bit depth + mask_val = 0xFFFFFFFF if bit_depth == 32 else 0xFFFF + hex_values = [f"0x{(row << left_shift) & mask_val:{hex_format}}" for row in rows] char_repr = f"'{char}'" if char not in ["'", "\\"] else f"'\\{char}'" print(f"\t[0x{char_code:02x} - FONT_START_CHAR] = {{") @@ -118,7 +143,10 @@ def generate_c_table(font_path): print("};") if __name__ == "__main__": - if len(sys.argv) < 2: - print(f"Usage: python3 {sys.argv[0]} ") - sys.exit(1) - generate_c_table(sys.argv[1]) + parser = argparse.ArgumentParser(description="Generate bitmapped font table.") + parser.add_argument("font_path", help="Path to TTF/OTF font file") + parser.add_argument("--width", type=int, default=24, help="Canvas width (default: 24)") + parser.add_argument("--height", type=int, default=32, help="Canvas height (default: 32)") + + args = parser.parse_args() + generate_c_table(args.font_path, args.width, args.height)