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)