Untitled

 avatar
unknown
python
a year ago
8.1 kB
15
Indexable
import xml.etree.ElementTree as ET
import os
import sys
import struct
from pathlib import Path

def parse_fnt_xml(fnt_path):
    """Parse BMFont XML file and extract glyphs, kerning pairs, and font info."""
    try:
        tree = ET.parse(fnt_path)
        root = tree.getroot()

        # Extract font info
        info = root.find("info")
        font_height = int(info.attrib["size"])

        # Extract common info
        common = root.find("common")
        texture_width = int(common.attrib["scaleW"])
        texture_height = int(common.attrib["scaleH"])
        num_pages = int(common.attrib["pages"])

        # Extract glyphs
        glyphs = []
        for char in root.find("chars").findall("char"):
            glyph = {
                "code": int(char.attrib["id"]),
                "x": int(char.attrib["x"]),
                "y": int(char.attrib["y"]),
                "width": int(char.attrib["width"]),
                "height": int(char.attrib["height"]),
                "offsetX": int(char.attrib["xoffset"]),
                "offsetY": int(char.attrib["yoffset"]),
                "advance": int(char.attrib["xadvance"]),
                "page": int(char.attrib["page"]),
            }
            glyphs.append(glyph)

        # Extract kerning pairs
        kernings = []
        kerning_elem = root.find("kernings")
        if kerning_elem is not None:
            for kerning in kerning_elem.findall("kerning"):
                kernings.append({
                    "first": int(kerning.attrib["first"]),
                    "second": int(kerning.attrib["second"]),
                    "amount": int(kerning.attrib["amount"]),
                })

        return glyphs, kernings, font_height, texture_width, texture_height, num_pages

    except Exception as e:
        print(f"Error parsing FNT file {fnt_path}: {e}")
        return None, None, None, None, None, None

def check_dds_files(dds_folder, num_pages, page_names):
    """Check if DDS files exist and match the number of pages."""
    dds_files = [f for f in os.listdir(dds_folder) if f.endswith(".dds")]
    expected_dds = [name if name.endswith(".dds") else f"{name}.dds" for name in page_names]
    
    if len(dds_files) != num_pages:
        print(f"Error: Number of DDS files ({len(dds_files)}) does not match pages ({num_pages})")
        return False
    
    for expected in expected_dds:
        if expected not in dds_files:
            print(f"Error: Missing DDS file {expected}")
            return False
    return True

def save_ccm_file(out_path, font_height, texture_width, texture_height, num_pages, glyphs, kernings):
    """Save CCM2 binary file based on CCM2 structure."""
    try:
        # Calculate offsets and file size
        header_size = 24  # sizeof(CCM2): 4+4+2+2+2+2+2+2+4+4+2+2 = 24 bytes
        tex_region_size = 8  # sizeof(TexRegion): 2+2+2+2 = 8 bytes
        glyph_size = 20  # sizeof(Glyph): 4+4+2+2+2+2+4+4 = 20 bytes
        kerning_size = 8  # sizeof(KerningPair): 2+2+4 = 8 bytes

        tex_region_offset = header_size  # TexRegions start right after header
        glyph_offset = tex_region_offset + len(glyphs) * tex_region_size  # Glyphs after TexRegions
        kerning_offset = glyph_offset + len(glyphs) * glyph_size  # Kernings after Glyphs
        file_size = kerning_offset + 4 + len(kernings) * kerning_size  # +4 for numKernings

        with open(out_path, "wb") as f:
            # Write CCM2 header
            f.write(struct.pack(
                "<IIHHHHHBBIIHH",
                2,                  # format (assuming 2 for CCM2)
                file_size,          # fileSize
                font_height,        # fontHeight
                texture_width,      # textureWidth
                texture_height,     # textureHeight
                len(glyphs),        # texRegionCount (same as glyph count)
                len(glyphs),        # glyphCount
                0,                  # pad[0]
                0,                  # pad[1]
                tex_region_offset,  # texRegionOffset
                glyph_offset,       # glyphOffset
                0,                  # alignment (not used in FNT)
                num_pages           # textureCount
            ))

            # Write TexRegions
            for glyph in glyphs:
                f.write(struct.pack(
                    "<HHHH",
                    glyph["x"],                    # x1
                    glyph["y"],                    # y1
                    glyph["x"] + glyph["width"],   # x2
                    glyph["y"] + glyph["height"]   # y2
                ))

            # Write Glyphs
            for idx, glyph in enumerate(glyphs):
                tex_region_offset = header_size + idx * tex_region_size
                f.write(struct.pack(
                    "<iihhhhii",
                    glyph["code"],         # code (int)
                    tex_region_offset,     # texRegionOffset (int)
                    glyph["page"],         # textureIndex (short)
                    glyph["offsetX"],      # preSpace (short)
                    glyph["width"],        # width (short)
                    glyph["advance"],      # advance (short)
                    0,                     # iVar10 (int)
                    0                      # iVar14 (int)
                ))

            # Write Kerning Pairs
            f.write(struct.pack("<i", len(kernings)))  # numKernings
            for kerning in kernings:
                f.write(struct.pack(
                    "<HHi",
                    kerning["first"],   # first (uint16)
                    kerning["second"],  # second (uint16)
                    kerning["amount"]   # amount (int)
                ))

        print(f"Generated CCM file: {out_path}")
        return True
    except Exception as e:
        print(f"Error saving CCM file {out_path}: {e}")
        return False

def save_layout_file(out_path, glyphs):
    """Save layout text file for debugging."""
    try:
        with open(out_path, "w", encoding="utf-8") as f:
            for glyph in glyphs:
                f.write(
                    f"code={glyph['code']}, textureId={glyph['page']}, prespace={glyph['offsetX']}, "
                    f"width={glyph['width']}, advance={glyph['advance']}, "
                    f"top=({glyph['x']}, {glyph['y']}), bottom=({glyph['x'] + glyph['width']}, {glyph['y'] + glyph['height']})\n"
                )
        print(f"Generated layout file: {out_path}")
    except Exception as e:
        print(f"Error saving layout file {out_path}: {e}")
        return False
    return True

def main():
    if len(sys.argv) < 3:
        print("Usage: python ccmfontbuilder.py input.fnt dds_folder")
        sys.exit(1)

    fnt_path = sys.argv[1]
    dds_folder = sys.argv[2]

    # Parse FNT XML
    glyphs, kernings, font_height, texture_width, texture_height, num_pages = parse_fnt_xml(fnt_path)
    if glyphs is None:
        sys.exit(1)

    # Get page names from FNT
    tree = ET.parse(fnt_path)
    page_names = [page.attrib["file"] for page in tree.getroot().find("pages").findall("page")]

    # Check DDS files
    if not check_dds_files(dds_folder, num_pages, page_names):
        sys.exit(1)

    # Create output directory
    filename = Path(fnt_path).stem
    out_dir = Path("Out") / filename
    out_dir.mkdir(parents=True, exist_ok=True)

    # Save layout file
    layout_path = out_dir / f"{filename}.txt"
    if not save_layout_file(layout_path, glyphs):
        sys.exit(1)

    # Save CCM file
    ccm_path = out_dir / f"{filename}.ccm"
    if not save_ccm_file(ccm_path, font_height, texture_width, texture_height, num_pages, glyphs, kernings):
        sys.exit(1)

    print(f"Success: Generated CCM from FNT: {ccm_path} with {num_pages} textures. Kerning pairs: {len(kernings)} included in CCM")

if __name__ == "__main__":
    main()
Editor is loading...
Leave a Comment