From 8790998fa5ec1c64dd732760cdf5a6627cb87caa Mon Sep 17 00:00:00 2001 From: Subrata Banik Date: Sun, 8 Feb 2026 00:23:37 +0530 Subject: [PATCH] util/font: Add bitmapped font generation utility This adds generate_font.py, a Python utility designed to convert standard TTF/OTF files into a compact bitmapped format suitable for firmware framebuffers. Key features: - Generates a left-aligned (normalized) bitmapped table to eliminate dead space in font glyphs. - Outputs a character width table to support proportional spacing. - Exports FONT_HEIGHT and FONT_WIDTH macros for C synchronization. - Limits output to printable ASCII (32-126) to minimize binary bloat. The tool uses the Pillow (PIL) library to rasterize glyphs and is intended to be used during the build process to generate C source files for splash screen text rendering. Usage: python util/font/generate_font.py > font_table.c Change-Id: Iec8907f1a5f24d61822230f6a22295c8382d2229 Signed-off-by: Subrata Banik Reviewed-on: https://review.coreboot.org/c/coreboot/+/91122 Tested-by: build bot (Jenkins) Reviewed-by: Kapil Porwal --- util/font/generate_font.py | 124 +++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 util/font/generate_font.py diff --git a/util/font/generate_font.py b/util/font/generate_font.py new file mode 100644 index 0000000000..4af3ed0434 --- /dev/null +++ b/util/font/generate_font.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-or-later + +""" +This utility generates a compressed bitmapped font table from a TTF/OTF file. +The output is a C source file containing: +1. Font dimensions and range macros. +2. A normalized (left-aligned) bitmapped font table. +3. A character width table for proportional spacing. + +Usage: + python generate_font.py > font_table.c +""" + +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): + if not os.path.exists(font_path): + print(f"Error: Font file '{font_path}' not found.", file=sys.stderr) + sys.exit(1) + + try: + 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) + + # Header for the generated C file + print("/* SPDX-License-Identifier: GPL-2.0-or-later */\n") + 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(" */\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_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] = {{") + + for char_code in range(START_CHAR, END_CHAR + 1): + char = chr(char_code) + image = Image.new("1", (CANVAS_WIDTH, CANVAS_HEIGHT), 0) + draw = ImageDraw.Draw(image) + draw.text((0, Y_OFFSET), char, font=font, fill=1) + + pixels = list(image.getdata()) + rows = [] + global_mask = 0 + + for y in range(CANVAS_HEIGHT): + row_val = 0 + base = y * CANVAS_WIDTH + for x in range(CANVAS_WIDTH): + if pixels[base + x]: + 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 + break + actual_width = ((CANVAS_WIDTH - 1) - left_shift) - rightmost_bit + 1 + else: + actual_width = 6 # Default space width + + 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] + char_repr = f"'{char}'" if char not in ["'", "\\"] else f"'\\{char}'" + + print(f"\t[0x{char_code:02x} - FONT_START_CHAR] = {{") + for i in range(0, len(hex_values), 8): + line = ", ".join(hex_values[i:i+8]) + print(f"\t\t{line},") + print(f"\t}}, /* {char_repr} */") + + print("};\n") + + # 3. Output Width Table + print(f"const uint8_t font_widths[FONT_NUM_CHARS] = {{") + for i, w in enumerate(widths): + char_code = i + START_CHAR + print(f"\t[0x{char_code:02x} - FONT_START_CHAR] = {w},") + 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])