Untitled
unknown
python
a year ago
8.1 kB
16
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