WE10JL: Conversión de Texturas de Fuentes a TTF Vectorial con Python

WE10JL REVERSE ENGINEERING & MODDING

Conversión de Fuentes Bitmap a TrueType (.TTF) en Python

Análisis técnico de extracción de texturas desentrelazadas y compilación vectorial OpenType para Windows.

1. Mapeo de Archivos BMP y Estructura ASCII

Las texturas de la tipografía extraídas mediante WE Picture Decoder generan una secuencia numerada de archivos .bmp. Para garantizar la correspondencia exacta con el teclado de Windows, mapeamos los índices según la tabla ASCII estándar:

Rango de Archivos Mapeo de Caracteres Offset ASCII / Código
66.bmp - 75.bmp Números (0 al 9) 0x30 - 0x39 48 - 57
76.bmp - 101.bmp Alfabeto Mayúsculas (A a la Z) 0x41 - 0x5A 65 - 90
102.bmp - 127.bmp Alfabeto Minúsculas (a a la z) 0x61 - 0x7A 97 - 122
2. Inversión del Eje Y y Escala Vectorial

A diferencia de las imágenes matriciales donde el punto (0,0) está arriba a la izquierda, las fuentes TrueType sitúan la línea de base en la esquina inferior izquierda. La conversión de coordenadas se define mediante la fórmula:

vy = (altura_imagen - y - 1) * scale

Donde scale = 32 proyecta la matriz de la textura a la caja em estándar de 1024 unitsPerEm.

3. Script de Automatización (generar_ttf.py)
import os
from PIL import Image
from fontTools.fontBuilder import FontBuilder
from fontTools.pens.ttGlyphPen import TTGlyphPen

FOLDER_PATH = r"C:\temp"
OUTPUT_TTF = r"C:\temp\WE10_Font.ttf"

def crear_ttf_preciso():
    glyph_order = [".notdef"]
    glyphs = {}

    # Glifo por defecto (.notdef)
    pen = TTGlyphPen(None)
    pen.moveTo((0, 0))
    pen.lineTo((512, 0))
    pen.lineTo((512, 1024))
    pen.lineTo((0, 1024))
    pen.closePath()
    glyphs[".notdef"] = pen.glyph()

    scale = 32
    metrics = {".notdef": (512, 0)}
    cmap = {32: ".notdef"}

    mapeo_archivos = {}
    for i, num in enumerate("0123456789"): mapeo_archivos[66 + i] = ord(num)
    for i, char in enumerate("ABCDEFGHIJKLMNOPQRSTUVWXYZ"): mapeo_archivos[76 + i] = ord(char)
    for i, char in enumerate("abcdefghijklmnopqrstuvwxyz"): mapeo_archivos[102 + i] = ord(char)

    for num_archivo, ascii_code in mapeo_archivos.items():
        path = os.path.join(FOLDER_PATH, f"{num_archivo}.bmp")
        if not os.path.exists(path): continue

        glyph_name = f"char_{ascii_code}"
        glyph_order.append(glyph_name)
        cmap[ascii_code] = glyph_name

        img = Image.open(path).convert("RGB")
        width, height = img.size
        pen = TTGlyphPen(None)
        tiene_pixeles = False

        for y in range(height):
            for x in range(width):
                r, g, b = img.getpixel((x, y))
                if (r + g + b) > 40:
                    tiene_pixeles = True
                    vx, vy = x * scale, (height - y - 1) * scale
                    pen.moveTo((vx, vy))
                    pen.lineTo((vx + scale, vy))
                    pen.lineTo((vx + scale, vy + scale))
                    pen.lineTo((vx, vy + scale))
                    pen.closePath()

        if not tiene_pixeles:
            pen.moveTo((0, 0))
            pen.closePath()

        glyphs[glyph_name] = pen.glyph()
        metrics[glyph_name] = (width * scale, 0)

    fb = FontBuilder(unitsPerEm=1024, isTTF=True)
    fb.setupGlyphOrder(glyph_order)
    fb.setupCharacterMap(cmap)
    fb.setupHorizontalMetrics(metrics)
    fb.setupGlyf(glyphs)

    fb.setupHead(unitsPerEm=1024)
    fb.setupHorizontalHeader(ascent=800, descent=-200)
    fb.setupNameTable({
        "familyName": "WE10 Retro Font",
        "styleName": "Regular",
        "uniqueFontIdentifier": "1.000;WE10;WE10RetroFont-Regular",
        "fullName": "WE10 Retro Font",
        "version": "Version 1.000",
        "psName": "WE10RetroFont-Regular",
    })
    fb.setupOS2(sTypoAscender=800, sTypoDescender=-200, sTypoLineGap=100, usWinAscent=800, usWinDescent=200)
    fb.setupPost()
    fb.save(OUTPUT_TTF)

if __name__ == "__main__":
    crear_ttf_preciso()
WE10JL Hex & Modding Lab © 2026 | Documentación técnica para PlayStation 2

Comentarios

Entradas populares de este blog

Tutorial: Cómo editar Scoreboards en WE10JL (SLPM_665.95)

Tutorial: WE10JL (SLPM_665.95) - Relación de KITS

Tutorial: WE10JL Estilo de Camara (SLPM_665.95)