Skip to content

Commit 5c19fc2

Browse files
committed
Added MGS Font support
1 parent 0ee1ce6 commit 5c19fc2

File tree

4 files changed

+388
-0
lines changed

4 files changed

+388
-0
lines changed

src/mgtools/__init__.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from mgtools.enumerators.game import Game
99
from mgtools.mg1.constants import RESOURCE_SECTION_COUNT as MG1_SECTION_COUNT
1010
from mgtools.mg2.constants import RESOURCE_SECTION_COUNT as MG2_SECTION_COUNT
11+
from mgtools.mgs_font import export_mgs_font, import_mgs_font
1112
from mgtools.resource import Resource
1213

1314
app = typer.Typer(help="Tools for modding PC ports of Metal Gear / Metal Gear 2")
@@ -21,6 +22,14 @@
2122
)
2223
ctxr_app = typer.Typer(help="Tools for working with ctxr textures")
2324
app.add_typer(ctxr_app, name="ctxr", help="Tools for working with ctxr textures")
25+
mgs_font_app = typer.Typer(
26+
help="Tools for working with MGS font files (MGS_Font_nht.raw)"
27+
)
28+
app.add_typer(
29+
mgs_font_app,
30+
name="mgs-font",
31+
help="Tools for working with MGS font files (MGS_Font.raw, MGS_Font_nht.raw)",
32+
)
2433

2534

2635
@res_app.command(
@@ -115,3 +124,37 @@ def ctxr_import(
115124
continue
116125

117126
import_ctxr(png_file, output_dir)
127+
128+
129+
@mgs_font_app.command(
130+
"export", help="Export MGS font file to a folder with glyph images and metadata."
131+
)
132+
def mgs_font_export(
133+
input_file: Annotated[
134+
Path, typer.Argument(exists=True, file_okay=True, readable=True)
135+
],
136+
output_dir: Annotated[
137+
Path | None, typer.Argument(file_okay=False, writable=True)
138+
] = None,
139+
):
140+
if output_dir is None:
141+
output_dir = input_file.parent / input_file.stem
142+
143+
export_mgs_font(input_file, output_dir)
144+
145+
146+
@mgs_font_app.command(
147+
"import", help="Import glyph images and metadata back into MGS font file."
148+
)
149+
def mgs_font_import(
150+
input_dir: Annotated[
151+
Path, typer.Argument(exists=True, dir_okay=True, readable=True)
152+
],
153+
output_file: Annotated[
154+
Path | None, typer.Argument(file_okay=True, writable=True)
155+
] = None,
156+
):
157+
if output_file is None:
158+
output_file = input_dir.parent / f"{input_dir.name}.raw"
159+
160+
import_mgs_font(input_dir, output_file)

src/mgtools/constants.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,7 @@
2020
TXTR_MAGIC = b"TXTR"
2121
TXTR_HEADER_SIZE = 0x84
2222
TXTR_ALIGNMENT = 32
23+
24+
FONT_MAGIC = b"TNOF"
25+
FONT_HEADER_SIZE = 0x18
26+
FONT_GLYPH_HEADER_SIZE = 5
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import struct
2+
from dataclasses import dataclass
3+
4+
from PIL import Image
5+
6+
7+
@dataclass
8+
class MgsGlyph:
9+
char_code: int
10+
width: int
11+
height: int
12+
bearing_x: int
13+
bearing_y: int
14+
advance: int
15+
pixel_data: bytes = b""
16+
17+
def to_image(self) -> Image.Image | None:
18+
# Convert glyph pixel data to a PIL Image (grayscale).
19+
if self.width == 0 or self.height == 0:
20+
return None
21+
return Image.frombytes("L", (self.width, self.height), self.pixel_data)
22+
23+
def data_bytes(self) -> bytes:
24+
# Serialize glyph data (5-byte header + pixel data).
25+
return (
26+
struct.pack(
27+
"BBBBB",
28+
self.width,
29+
self.height,
30+
self.bearing_x,
31+
self.bearing_y,
32+
self.advance,
33+
)
34+
+ self.pixel_data
35+
)

0 commit comments

Comments
 (0)