# gsfnt
CLI tool that packs all images in a directory into a **BMFont** text `.fnt` file plus a single merged **RGBA PNG** atlas. Each glyph’s Unicode code point is taken from the **first character** of the image filename stem (the part before the extension).
## Install
```bash
cargo install gsfnt
```
Or build from this repository:
```bash
cargo build --release
# binary: target/release/gsfnt
```
## Usage
```bash
gsfnt --input /path/to/images --output /path/to/out/font
```
This writes:
- `font.fnt` — AngelCode-style BMFont (text format)
- `font.png` — atlas image referenced by the `.fnt`
If `--output` already ends with `.fnt`, that path is used as-is and the PNG is the same path with `.png`.
### Options
| `-i`, `--input` | Directory containing source images |
| `-o`, `--output` | Output prefix (see above) |
| `--max-width` | Maximum row width for shelf packing (default: `4096`) |
| `--align` | Vertical alignment direction: `top`, `center`, `bottom` (default: `bottom`) |
| `--align-by` | What the alignment is measured against: `auto`, `box`, `ink` (default: `auto`) |
| `--ink-threshold` | Alpha at or above which a pixel counts as ink, `1`-`255` (default: `128`) |
| `--padding` | Transparent gap in pixels between glyphs in the atlas (default: `1`) |
### Supported image extensions
`png`, `jpg`, `jpeg`, `webp`, `gif`, `bmp`
### Filename → glyph
- `A.png` → glyph for `A` (U+0041)
- `中.png` → glyph for `中`
- `emoji_rest.png` → glyph for the first character only (`e`)
Duplicate code points (two files mapping to the same first character) are rejected with an error listing both paths.
On macOS and Windows filenames are case-insensitive, so `a.png` and `A.png` cannot live in the same directory. Characters that are illegal in filenames (`/`, `:`) cannot be expressed either.
### Vertical alignment
`--align` picks the direction; `--align-by` picks what is aligned.
- `box` compares the source image rectangles.
- `ink` compares the glyphs' visible pixels, ignoring anything fainter than `--ink-threshold`.
- `auto` (the default) uses `box` when every source image has the same height, and `ink` when they differ.
The distinction matters because art exported with "trim transparent pixels" leaves each glyph a different height, and a single almost-invisible antialiased row is enough to shift a glyph by one pixel. Aligning image boxes then makes a line of digits visibly wobble. Aligning the ink does not. When all sources share one canvas size, `box` is used so that deliberate empty space (descenders, for instance) is preserved.
The tool prints which basis it picked.
### Font metrics (output)
- `lineHeight` / `base` = maximum image height among all glyphs
- `xoffset` = 0
- `xadvance` = glyph width
- `yoffset`, with `--align-by box` = `lineHeight - height` for `bottom`, `0` for `top`, half the leftover for `center`
- `yoffset`, with `--align-by ink` = the shift that puts every glyph's ink edge on the same row; the largest edge is the anchor, so no offset is negative
A glyph's box may reach past `lineHeight` under `ink` alignment, which is a normal descender in BMFont terms.
### Godot notes
The `common` line is written with `alphaChnl=0 redChnl=0 greenChnl=0 blueChnl=0`, which is what Godot needs to import the atlas as an RGBA color font. Any other value for `alphaChnl` makes Godot read the font as monochrome-with-outline, rebuild it from the red channel and force it white, which destroys colored fonts. Versions up to 0.1.1 wrote `alphaChnl=1` and need this line corrected by hand.
`--padding` defaults to 1 because Godot scales bitmap fonts with linear filtering, and touching glyphs bleed into each other when scaled.
## License
MIT. See [LICENSE](LICENSE).