From d4c3d979178762a742b87c9b2c253efad6276539 Mon Sep 17 00:00:00 2001 From: Subrata Banik Date: Thu, 12 Feb 2026 15:04:24 +0530 Subject: [PATCH] util/font: Transition to 8-bit anti-aliased font generation Update generate_font.py to produce 8-bit alpha maps instead of 1-bit packed bitmaps. This enables text smoothing (anti-aliasing) during framebuffer rendering by providing pixel intensity values (0-255). Key changes: - Switch PIL image mode from "1" (monochrome) to "L" (8-bit grayscale). - Change C data type from uint32_t bit-packed rows to uint8_t byte arrays. - Implement vertical centering logic using font metrics (ascent/descent). - Add glyph clipping detection and warnings for both width and height. - Format C output so each source line represents one glyph row. Change-Id: Iec8a0123456789abcdef0123456789abcdef0123 Signed-off-by: Subrata Banik Reviewed-on: https://review.coreboot.org/c/coreboot/+/91178 Reviewed-by: Kapil Porwal Tested-by: build bot (Jenkins) --- util/font/generate_font.py | 101 +++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 56 deletions(-) diff --git a/util/font/generate_font.py b/util/font/generate_font.py index b301a2e533..81b45b1689 100644 --- a/util/font/generate_font.py +++ b/util/font/generate_font.py @@ -2,10 +2,10 @@ # SPDX-License-Identifier: GPL-2.0-or-later """ -This utility generates a compressed bitmapped font table from a TTF/OTF file. +This utility generates an anti-aliased (smoothed) 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. +2. An 8-bit alpha map font table (0=transparent, 255=opaque). 3. A character width table for proportional spacing. Usage: @@ -26,11 +26,10 @@ def generate_c_table(font_path, canvas_width, canvas_height): 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) + # RESTRICTION: Width and Height checks for firmware safety + if canvas_width > 64 or canvas_height > 64: + print(f"Warning: Large dimensions ({canvas_width}x{canvas_height}) " + "will consume significant flash memory.", file=sys.stderr) # Validation: Basic sanity check for positive dimensions if canvas_width <= 0 or canvas_height <= 0: @@ -39,7 +38,6 @@ def generate_c_table(font_path, canvas_width, canvas_height): # 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) @@ -47,6 +45,16 @@ def generate_c_table(font_path, canvas_width, canvas_height): print(f"Error: Could not open font file '{font_path}'.", file=sys.stderr) sys.exit(1) + # Check for vertical clipping based on font metrics + ascent, descent = font.getmetrics() + total_font_height = ascent + descent + if total_font_height > canvas_height: + print(f"Warning: Font vertical size ({total_font_height}px) exceeds " + f"canvas height ({canvas_height}px).", file=sys.stderr) + y_offset = 0 + else: + y_offset = (canvas_height - total_font_height) // 2 + # Header for the generated C file print("/* SPDX-License-Identifier: GPL-2.0-or-later */\n") print("/*") @@ -65,72 +73,50 @@ def generate_c_table(font_path, canvas_width, canvas_height): widths = [] - # 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 + print(f"const uint8_t font_table[FONT_NUM_CHARS][FONT_HEIGHT * FONT_WIDTH] = {{") - # 2. Generate packed font table - print(f"const {data_type} font_table[FONT_NUM_CHARS][FONT_HEIGHT] = {{") + clipped_glyphs = [] 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("L", (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=255) pixels = list(image.getdata()) - rows = [] - global_mask = 0 + + leftmost = canvas_width + rightmost = 0 + has_pixels = False for y in range(canvas_height): - row_val = 0 - base = y * canvas_width for x in range(canvas_width): - if pixels[base + 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 + if pixels[y * canvas_width + x] > 0: + if x < leftmost: leftmost = x + if x > rightmost: rightmost = x + has_pixels = True - # Dead space removal / Normalization - left_shift = 0 - actual_width = 0 - - 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 - - # 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 + if has_pixels: + actual_width = rightmost - leftmost + 1 + if actual_width >= canvas_width: + clipped_glyphs.append(char) else: actual_width = canvas_width // 3 # Default width for space widths.append(actual_width) - # 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] = {{") - for i in range(0, len(hex_values), 8): - line = ", ".join(hex_values[i:i+8]) - print(f"\t\t{line},") + + # Format with line breaks every 8 entries + for i in range(0, len(pixels), canvas_width): + row = pixels[i : i + canvas_width] + for j in range(0, len(row), 8): + chunk = row[j : j + 8] + line = ", ".join([f"0x{p:02x}" for p in chunk]) + print(f"\t\t{line},") + print(f"\t}}, /* {char_repr} */") print("};\n") @@ -142,8 +128,11 @@ def generate_c_table(font_path, canvas_width, canvas_height): print(f"\t[0x{char_code:02x} - FONT_START_CHAR] = {w},") print("};") + if clipped_glyphs: + print(f"/* Warning: Characters clipped: {' '.join(clipped_glyphs)} */", file=sys.stderr) + if __name__ == "__main__": - parser = argparse.ArgumentParser(description="Generate bitmapped font table.") + parser = argparse.ArgumentParser(description="Generate anti-aliased 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)")