6.4 Implementation
import argparse
from PIL import Image, ImageDraw, ImageFont
import struct
import os
def generate_font_atlas(font_path, size, chars, tex_w, tex_h, padding, fg_color, bg_color):
# Load font
font = ImageFont.truetype(font_path, size)
# Calculate cell size
max_width = 0
max_height = 0
glyphs = []
for char_code in chars:
# Get character
if char_code == 0:
# Null character - use space
char = ' '
else:
char = chr(char_code)
# Get bounding box
bbox = font.getbbox(char)
width = bbox[2] - bbox[0]
height = bbox[3] - bbox[1]
max_width = max(max_width, width)
max_height = max(max_height, height)
cell_w = max_width + 2 * padding
cell_h = max_height + 2 * padding
# Create atlas
atlas = Image.new('RGBA', (tex_w, tex_h), bg_color)
# Pack glyphs
x, y = padding, padding
glyph_info = []
for char_code in chars:
if char_code == 0:
char = ' '
else:
char = chr(char_code)
# Render character
char_img = Image.new('RGBA', (cell_w, cell_h), bg_color)
draw = ImageDraw.Draw(char_img)
draw.text((padding, padding), char, font=font, fill=fg_color)
# Paste to atlas
if x + cell_w > tex_w:
x = padding
y += cell_h
if y + cell_h > tex_h:
raise ValueError("Font atlas too small")
atlas.paste(char_img, (x, y))
# Store glyph info
glyph_info.append({
'codepoint': char_code,
'tex_x': x,
'tex_y': y,
'tex_w': cell_w,
'tex_h': cell_h,
'bearing_x': padding - bbox[0],
'bearing_y': padding - bbox[1],
'advance': cell_w
})
x += cell_w
return atlas, glyph_info, cell_w, cell_h
def save_d3f(atlas, glyph_info, output_path, cell_w, cell_h):
tex_w, tex_h = atlas.size
# Convert to ARGB1555
argb1555_data = bytearray()
for y in range(tex_h):
for x in range(tex_w):
r, g, b, a = atlas.getpixel((x, y))
# Convert RGBA8888 to ARGB1555
argb1555 = ((a >> 7) << 15) | ((r >> 7) << 10) | ((g >> 7) << 5) | (b >> 7)
argb1555_data.extend(struct.pack('<H', argb1555))
# Write header
num_glyphs = len(glyph_info)
header = struct.pack('<4sHHHBBH',
b'D3TF', # Magic
1, # Version
tex_w, tex_h, # Texture dimensions
cell_w, cell_h, # Cell dimensions
0, # Reserved
num_glyphs, # Number of glyphs
16 # Glyph entry size
)
header += b'\x00' * 6 # Reserved
# Write glyph table
glyph_data = bytearray()
for glyph in glyph_info:
entry = struct.pack('<HHBBbbBB',
glyph['tex_x'], glyph['tex_y'],
glyph['tex_w'], glyph['tex_h'],
glyph['bearing_x'], glyph['bearing_y'],
glyph['advance'], 0 # Reserved
)
glyph_data.extend(entry)
# Write file
with open(output_path, 'wb') as f:
f.write(header)
f.write(glyph_data)
f.write(argb1555_data)