Bitmap Font Generator — BMFont .fnt from a Sheet or TTF
Three ways in, one font out: a fixed-grid glyph sheet, the same sheet with widths measured from the ink, or a font file rendered on your device — written as BMFont text that a separate parser reads back identically.
Draw pixel glyphs locally or import a PNG sheet, BDF, or editable project. The maker writes one PNG atlas and matching BMFont text, XML and version-3 binary descriptors; editable projects also include the v2 source and, within stated limits, a TrueType pixel-outline font. A live missing-character count and kerning preview help check the actual text you plan to render. No file is uploaded. Exported text, XML, binary, PNG and TTF from two CC0 BDFs were independently reopened; engine loading of the new BDF path remains a separate check.

- Nine-sliceMeasured· Pillow
- Bitmap fontMeasured· independent .fnt parser
- Button statesMeasured· Pillow
- Engine importUNVERIFIED
What a .fnt file says, line by line
info names the face and size and records padding and spacing. common holds lineHeight (the distance in pixels between two lines of text), base (pixels from the top of a line to the baseline), scaleW and scaleH (the texture size) and pages. page id=0 file="font.png" names the texture, relative to the .fnt. Each char line gives id (the Unicode code point in decimal), x y width height (the glyph's rectangle in the texture), xoffset yoffset (where to draw that rectangle relative to the pen position and the top of the line) and xadvance (how far the pen moves afterwards). kerning first second amount lines adjust particular pairs; The editable project records pair kerning, while a plain image sheet has no kerning metadata to import.
To draw a string an engine starts the pen at x = 0, and for every character copies its rectangle to (pen + xoffset, line top + yoffset) and adds xadvance to the pen; the next line starts lineHeight lower. In the fixed-grid mode every advance is the cell width, which suits monospaced pixel fonts. The measured mode keeps only each cell's opaque pixels (alpha above 8), puts them back with the offsets and advances by the ink's right edge plus the glyph spacing; an empty cell such as the space still advances.
Character sets decide the atlas size. ASCII is 95 characters (U+0020–U+007E) and Latin-1 191; precomposed Hangul alone is 11,172 syllables and the main CJK ideograph block 20,992 code points, so the Korean and Japanese presets take only the characters that occur in text you paste. A signed distance field (SDF) stores, per texel, the distance to the glyph's edge instead of its coverage, so a shader can cut a sharp edge at other sizes; Nerulio encodes 128 as the edge and spread pixels of distance across the byte range, computes the exact distance per glyph at 4× resolution, and ships a single-channel SDF, not MSDF.
- lineHeight
- Pixels from one line of text to the next.
- base
- Pixels from the top of a line down to the baseline.
- xoffset / yoffset
- Where the glyph rectangle is drawn relative to the pen and the line top.
- xadvance
- How far the pen moves after the glyph.
- SDF spread
- How many pixels of distance the 0–255 range covers; 128 is the glyph edge.
Example: the same three glyphs in grid and measured mode
A 24 × 8 sheet with 8 × 8 cells for A, i and a space, baseline 7. These are the lines the maker writes:
common lineHeight=8 base=7 scaleW=24 scaleH=8 pages=1
grid char id=65 x=0 y=0 width=8 height=8 xoffset=0 yoffset=0 xadvance=8
char id=105 x=8 y=0 width=8 height=8 xoffset=0 yoffset=0 xadvance=8
measured char id=65 x=1 y=1 width=5 height=6 xoffset=1 yoffset=1 xadvance=7 (1 + 5 + spacing 1)
char id=105 x=11 y=1 width=1 height=6 xoffset=3 yoffset=1 xadvance=5 (3 + 1 + 1)
char id=32 x=16 y=0 width=0 height=0 xoffset=0 yoffset=0 xadvance=3 (empty cell: round(8 ÷ 3))
draw "iA i" measured i at 0 + 3 = 3 → pen 5; A at 5 + 1 = 6 → pen 12; space → pen 15; i at 18 → width 20 px
draw "iA i" grid 4 glyphs × 8 = 32 pxThe measured font sets i 5 px apart instead of 8, which is why proportional text looks natural. font.json carries the same numbers as glyphs[].xAdvance, xOffset and yOffset.
What it does
- Fixed grid with exact Unicode coordinates (non-Latin glyphs included), or measured widths with x/y offsets and advances from the ink.
- Font file (TTF/OTF) loaded locally with FontFace, rendered into a uniform grid, advances from the font's own metrics.
- Character-set builder: characters used in your text, ASCII (95), Latin-1, Korean or Japanese characters found in your text.
- SDF (Beta): an exact distance transform per glyph; no MSDF, no kerning, one page.
How to do it
- Drop a glyph sheet or choose a font file.
- Set cell size, baseline and the characters.
- Choose fixed, measured or font-file mode.
- Export font.png + font.fnt + font.json.
Load the font in an engine
- Keep
font.fntandfont.pngtogether: thepageline names the PNG by a relative file name, so rename both or neither. - Godot 4: copy both files into the project; Godot imports the
.fntas a bitmap font. Assign it to a Label or other Control as its font theme override. Bitmap fonts have a fixed design size, so draw pixel fonts at that size or whole multiples. - PixiJS 8:
await Assets.load('font.fnt'), then createnew BitmapText({ text, style: { fontFamily: 'Nerulio Grid' } })— the family is thefacein theinfoline (Nerulio Grid, Nerulio Measured, or Nerulio plus your font file name). - Phaser: use
this.load.bitmapFont(key, 'font.png', 'font.xml'). The ZIP contains the XML descriptor alongside the PNG; test the loaded result in your Phaser version.
Check the result
- Open
font.fntin a text editor:chars countequals the number ofcharlines, and every rectangle lies insidescaleW × scaleH. - The sample line under the sheet is drawn with the font's own metrics: letters that overlap or drift apart there will do the same in the engine.
- Run the missing glyph checker with your localisation file against this font before shipping.
Troubleshooting
| Symptom | Likely cause | How to check | Fix |
|---|---|---|---|
| The engine loads the font but shows nothing | font.png was renamed or moved away from the .fnt | The page id=0 file= line and the actual file name | Put the PNG next to the .fnt with the name the page line gives |
| Phaser fails to parse the font | Phaser's load.bitmapFont expects the XML variant of BMFont | The console error when the loader parses the data | Use the included font.xml next to font.png, then check Phaser console and file paths. |
| Letters touch or sit too far apart in measured mode | The glyph spacing value, or faint pixels above alpha 8 widening a glyph's ink box | Compare xadvance of a narrow and a wide glyph in the .fnt | Change the glyph spacing, or clean faint pixels off the sheet |
Font-file mode: info size is larger than the size you chose | The size written is the cell height (ascent + descent + padding), not the font size you typed | Compare info size with the Font size field | Where an engine uses size, set it by hand to the size you rendered |
| Font-file mode: some letters look like another typeface | A character your TTF lacks was drawn by the browser's fallback font and baked into the sheet | Look at the glyph sheet for letters in a different style | Remove those characters, or pick a font file that contains them |
| Font-file mode: a pixel font comes out blurry | Canvas text is anti-aliased, so edges get partial alpha | Zoom into the sheet: grey edge pixels | Draw the pixel font as a sheet and use the grid or measured mode |
| Grid mode says the character order does not fit | More characters than cells, a repeated character, or an empty order | Columns × rows of the sheet against the number of characters | Remove duplicates or use a larger sheet; a strip narrower than a cell at the edge is ignored |
Other ways to do it
| Option | When it is the better choice |
|---|---|
| Godot's own image-font import (import type Font Data (Image Font)) | The font is only for Godot and the sheet is in a simple order: no extra files, and the import dock also takes character ranges, margins and kerning pairs. |
| AngelCode BMFont or another BMFont-compatible generator | You need verified multi-page atlases, effects, or advanced font features; this editor currently writes one atlas page. |
| A dynamic font (the TTF itself) in the engine | Text size changes a lot or the language needs thousands of glyphs; Godot documents MSDF rendering for dynamic fonts, not for bitmap fonts. |
Outputs and how each was checked
"Measured" means the downloaded file was opened again outside the page (Pillow, numpy or an independent parser) and the property was measured. Loading these files in a game engine was not tested.
| Target | Files | Check |
|---|---|---|
| Nine-slice | nine-slice.json · previews · SETUP.md | Measured PillowEvery resized preview keeps all four corner blocks unscaled: the same bytes in Chromium; in Firefox semi-transparent pixels can shift by one alpha-rounding step. 0 gaps, 0 overlaps. |
| Bitmap font | font.png · font.fnt (BMFont text) · font.json | Measured independent .fnt parserThe .fnt was read back by a parser written separately in the test: header and every glyph rect match. |
| Button states | states/*.png · strip · states.json | Measured PillowNormal equals the source (byte for byte in Chromium; in Firefox semi-transparent colour within one alpha-rounding step); every rect in states.json cuts exactly its state out of the strip. |
| Engine import | Godot NinePatchRect · Unity Sprite border · BMFont import | UNVERIFIEDNot run in Godot or Unity. |
Evidence
Nine-slice corners unscaled at 100×40, 300×80, 800×200 and 420×120; border suggestion 6·6·6·6 from a run of 12 identical columns; button states: normal equal to the source, disabled grey at alpha 128, focus ring exact; .fnt glyph records equal the JSON; missing glyphs from a .po listed exactly; black on white measured 21:1. Images pass through a browser canvas: Chromium returns the same bytes; Firefox rounds the colour of semi-transparent pixels to one premultiplied step (measured on a Kenney button: up to 4 levels at alpha 63), while alpha and opaque pixels stay exact.
Limits
- One atlas page; local TTF/OTF baking and BDF/project editing currently cap at 1,024 glyphs. No verified multi-page CJK output or OTF download; SDF remains Beta. A coverage count does not prove that Hangul composition is legible.
- The editable TTF uses pixel-square outlines; two CC0 BDFs reopened with fontTools, but default FreeType hinting moved the detached dots in i and j. Unity TextMeshPro loading is unverified.
- The existing local-font bake can use browser fallback glyphs and antialiasing. Inspect every downloaded atlas in the target engine.
- No kerning pairs, one font page, no MSDF; the SDF output is Beta.
- A TTF's cmap is not parsed, so the missing-glyph check needs a .fnt or the Lab's own font.
- One atlas page; touching elements count as one.
- Rendering inside Godot or Unity was not run: the engine numbers (patch margins, L-B-R-T border) are documented, not engine-tested.
Versions and sources
Measured in Chromium for docs/UI-LAB.md: a fixed-grid font writes char id=12354 x=8 y=0 width=8 height=8 for あ in a 16 × 8 font.png; an independent parser in the test read the .fnt header and every glyph record back equal to the JSON; measured mode gives tight rects, at least three distinct advances and an advancing empty cell; an ASCII-95 atlas at 32 px from a TTF built in 28 ms (350 × 380 sheet) and its SDF in 1174 ms. The font-file issues listed above are known, unfixed issues of this release. BMFont fields follow the AngelCode documentation; engine loading follows Godot 4.7, PixiJS 8 and the Phaser API.
Official documentation used
Questions
Does it make kerning pairs?
No. A bitmap sheet does not carry kerning data and inventing it would be wrong; the .fnt says kernings count=0.
Will Godot or Unity import it?
BMFont text is a common format, but import into Godot or Unity was not tested here.
Is it free, and do I need an account?
Everything on these pages is free and works without an account. There is no watermark and no export that is held back.
Are my sprites uploaded?
No. The Studio decodes, packs and exports in this browser tab. Projects are autosaved in the browser (IndexedDB) and can be saved as a .nerulio file.
Does it need Aseprite or an engine plugin installed?
No. .aseprite files are read and written in the browser, and each engine bundle carries what that engine needs (for Godot, the import settings and scene; for Unity and the Godot TileSet, a small editor script).