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 <subratabanik@google.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/91178
Reviewed-by: Kapil Porwal <kapilporwal@google.com>
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
This commit is contained in:
Subrata Banik 2026-02-12 15:04:24 +05:30
commit d4c3d97917

View file

@ -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)")