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:
parent
ce431ac056
commit
d4c3d97917
1 changed files with 46 additions and 57 deletions
|
|
@ -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)")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue