gsfnt 0.2.0

Pack a folder of images into BMFont (.fnt) and a merged PNG atlas
gsfnt-0.2.0 is not a library.

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

cargo install gsfnt

Or build from this repository:

cargo build --release
# binary: target/release/gsfnt

Usage

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

Option Description
-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.