A Python port of the original C#/SharpDX mgsfontgen-dx — a tool
for generating bitmap font atlases (and matching width tables) for MAGES-engine
(SC3) games, as consumed by LanguageBarrier.
It reproduces the original tool's layout, metrics, outline and width-table semantics using Pillow + numpy instead of DirectWrite/SharpDX, so output is not byte-identical to the original rasteriser but the behaviour is preserved.
- Bitmap font atlas generation in a 64-column grid (configurable).
- Normal cells
48×48/ 32×32, outline cells57×57(the original+9room for the outline stroke). A separate outline atlas is produced by stroking the glyph outlines in 7 passes with decreasing alpha. - Per-character widths measured with the game's
1.5×width multiplier, written one byte per character towidths.bin. - Compound characters: Private-Use-Area code points mapped to replacement strings (optionally horizontally stretched to fit a single cell).
- Two spacing controls per line: a coarse cell spacing (may be negative
for a condensed look) and a fine glyph spacing (free range — no upper/lower
limit — for precise tightness). The effective pitch is
cell + cell_spacing + glyph_spacing(clamped to ≥ 1 px). - Multi-line charsets: if the charset contains line breaks (CR
U+000D, LFU+000A, or CRLF), it is split into separate line blocks that never flow into one another. Each line keeps its own cell/glyph spacing, independently editable. - PNG or DDS output (DDS is uncompressed
BGRA8, matching the game's expected format). Optional alpha premultiplication. - A PySide6 GUI with a live preview, zoom controls, grid overlay and per-line spacing.
- Forced column count: the column count is normally derived from the output
width and the current pitch, but it can be forced (CLI
--columns, or the GUI Columns spinner in Layout & Format). Forcing it overrides the output-width derivation for both the preview and the generated atlas. - True pair kerning: when enabled, each glyph is shifted by its real
pair-kerning value with the preceding glyph (read from the font's
kern/GPOStables viafontTools). Applied visually — the width table is unchanged. - Per-character position adjustment: click any glyph in the GUI preview to
select it, then nudge it with the
X/Yspinners (applied on top of the shared baseline). Overrides are keyed by code point and affect every occurrence; the GUI can import/export them to a[0xXXXX]=dx,dyfile, and the CLI takes the same file via--adjustments. - Session save / load: the GUI can export the entire configuration (font, size, baseline, cell/spacing, per-line spacing, columns, kerning, outline, charset/compound/output paths, and per-character adjustments) to a JSON session file, and re-import it later to replicate a past output. See below.
- Python 3.10+
PillownumpyPySide6(GUI only; the CLI works without it)fontTools— used for true pair kerning (reads the font'skern/GPOStables). Optional in the sense that the tool still runs without it;--kerningsimply becomes a no-op.- A font resolvable by
fontconfig(fc-match) when passing a family name, or an explicit.ttf/.ttc/.otfpath.
Install the dependencies:
pip install Pillow numpy PySide6python3 fontgen.py generate --charset charset.utf8 \
--font-family "Noto Sans CJK JP" --font-size 38 \
--baseline-originx 1 --baseline-originy -7 \
--charset-size 5440 --image-format png --output-dir out
| Flag | Default | Description |
|---|---|---|
--charset |
(required) | Charset text file (UTF-8). |
--font-family |
Noto Sans CJK JP |
Font family name or a path to a font file. |
--compound-characters |
— | Compound-character table (see below). |
--adjustments |
— | Per-character position table, [0xXXXX]=dx,dy (dx/dy are pixel nudges). |
--font-size |
38 |
Font size in px. |
--baseline-originx |
1 |
Glyph baseline X offset within the cell. |
--baseline-originy |
-7 |
Glyph baseline Y offset within the cell. |
--charset-size |
5440 |
Max glyphs per output "page" (batch). Lines are never split across pages. |
--image-format |
png |
png or dds. |
--cell-width |
48 |
Normal cell width. |
--cell-height |
48 |
Normal cell height. |
--cell-spacing |
0 |
Coarse gap between cells (px); negative condenses. |
--glyph-spacing |
4 |
Fine gap between glyphs (px); free range. |
--output-width |
3072 |
Target atlas width; columns derived from it. |
--columns |
— | Force a column count (overrides --output-width). Default: derive from --output-width and pitch. |
--output-dir |
. |
Output directory. |
--no-premultiply |
— | Disable alpha premultiplication. |
--no-outline |
— | Skip the outline atlas. |
For each batch (page) a letter is appended: FONT_A.png, font-outline_A.png,
FONT_B.png, font-outline_B.png, … plus a single widths.bin containing all
widths concatenated (one byte per glyph, in flattened line order).
Use Private-Use-Area code points for compound glyphs and list their expansions
in a table file passed via --compound-characters (GUI: Compound table).
Each non-empty line is either:
[0xXXXX]=value
[0xAAAA-0xBBBB]=value
The mapped value string is rendered into one cell and, when its measured
width exceeds the normal cell divided by 1.5, it is horizontally stretched to
fit (matching the original mgsfontgen behaviour).
When the charset file contains line breaks, it is treated as multiple lines:
- Lines are split on CR
U+000D, LFU+000A, or CRLF. The break characters are not rendered. - Each line is laid out as a separate block — a line never flows into the next one.
- Each line stores its own
cell_spacing/glyph_spacing. Changing the spacing controls only affects the line currently being edited.
In the CLI, the same --cell-spacing / --glyph-spacing are applied
uniformly to every line. In the GUI, use the Line selector (in the
Layout & Format group) to pick which line's spacing you are editing; the
preview shows the active line.
Example: a charset edited with "shift+Enter" line breaks stores each line as a separate block, and different dialogue lines can be tightened or loosened independently.
Launch with:
python3 mgsfontgen_gui.pyThe .ui layout is defined in mgsfontgen_gui.ui. If you edit the .ui,
regenerate the Python binding:
pyside6-uic mgsfontgen_gui.ui -o ui_mgsfontgen_gui.py(Do not hand-edit ui_mgsfontgen_gui.py; it is generated.)
See USAGE.md for a walkthrough of every control.
| File | Purpose |
|---|---|
fontgen.py |
Core generator (FontAtlasGenerator, render_lines, generate) + CLI. |
mgsfontgen_gui.py |
PySide6 GUI (uses the generated UI). |
mgsfontgen_gui.ui |
Qt Designer layout file. |
ui_mgsfontgen_gui.py |
Generated from .ui (regenerate with pyside6-uic). |
charsets |
Sample charsets. |
To reproduce a past atlas exactly, use the GUI's Session group:
- Export session writes every generation-affecting setting to a JSON file
(
session.json). This includes the font family, font size, baseline X/Y, cell width/height, per-line cell/glyph spacing, forced Columns, kerning, image format, premultiplication, outline toggle, charset/compound/output-dir paths, and the full per-character adjustment table. - Import session reads that JSON back and re-applies all controls, reloads the charset/compound files, and restores per-line spacing + adjustments, then refreshes the preview.
The charset, compound and output-dir entries are stored as paths (same machine / working copy). If a path no longer exists on import, the field is still filled in so you can re-point it; per-line spacing is padded/truncated to the loaded charset's line count.
- Rasterisation uses Pillow/
numpyrather than DirectWrite, so pixel output is not byte-identical, but layout, metrics, outline passes and the width table are faithful. - The Python port adds the finer glyph spacing control and per-line spacing on top of the original's single spacing.
- A ZDR Hy3 model was used during the porting / testing process of this utility.