Toolkitly

Bitmap Font Builder

Slice a sheet of drawn characters into glyphs, say which character is which, then type something and watch it set. Export a BMFont .fnt or JSON. Free, no sign-up, and every pixel stays in your browser.

Nothing is uploaded — the sheet is sliced and set inside your browser.

How to turn a sheet into a font

Four steps, and the third is the one that goes wrong.

  1. 1Load the sheetDrag a PNG, WebP, GIF or JPEG onto the drop area, pick a file, or paste an image straight from the clipboard. Transparent background, characters in reading order.
  2. 2Let it find the charactersAutomatic detection works on any layout with gaps between the glyphs. If your sheet is a regular table of cells, switch to grid mode and give it the column and row count.
  3. 3Line up the character stringThe first box takes the first character. This is the step that goes wrong, and the preview is how you catch it — if it reads as gibberish, the string is out of step with the sheet.
  4. 4Set the spacing and exportTracking, line spacing, and how wide a space is. Then the .fnt, which goes next to the image it names.

Why a font sheet is not a sprite sheet

Finding the characters looks like finding animation frames, and most of it is: flood-fill the islands of solid pixels, sort them into reading order. The site's sprite sheet cutter does exactly that, and this tool reuses the same detection.

Then it diverges, because a glyph is often more than one island. The dot of an i does not touch its stem. The two dots of a : touch nothing. An = is two separate bars. Left alone, detection reports each as its own character and the whole string after it shifts.

A sprite sheet solves the same problem by merging islands that are close together — a character's detached sword belongs to the character. On a font sheet that rule is wrong: neighbouring letters are also close together, and merging by distance joins rn into something that is not a letter at all.

So this merges by horizontal containment instead. A dot sits directly over its stem, so one box is inside the other's column range. Two letters side by side are not, even when they lean over each other by a pixel. That one change is most of the difference between a sprite sheet cutter and a font slicer.

The thing that goes wrong

Nearly every problem with a generated bitmap font is the same problem: the character string is out of step with the sheet, and it is out of step by one.

A stray pixel in the margin counts as a glyph. A sheet that begins with a blank cell does not, because a blank cell has nothing to detect. An i whose dot was drawn a pixel too far to the left fails the containment test and becomes two characters. Any of these shifts every character after it, and the file is perfectly valid — it just names the wrong glyph for every letter past the mistake.

Which is why the preview is not a decoration. Type a word and read it. If it comes out as nonsense, the shift is somewhere before the first wrong letter, and the boxes drawn on the sheet show where. The tool also reports boxes that ran past the end of the string and characters with no box left, which usually names the problem outright.

When you need this

A face you drew yourself

A pixel font drawn in Aseprite is a sheet of characters and nothing else. This is the step between that and something an engine can set text with.

A font sheet with no metrics

Asset packs ship the PNG and leave you to work out the spacing. Import it, set the tracking by eye against real text, export the .fnt.

Changing the spacing of a font you have

Load the sheet, adjust tracking and line spacing until the sample text reads well at the size you use it, and re-export.

Checking a font before it ships

Type the strings your game actually shows — the longest menu item, a number with a decimal point — and see whether every character is there.

Export formats

.fnt — AngelCode's BMFont text format. One char line per glyph with its rectangle, offsets and advance, plus the info, common and page lines every parser expects. It refers to your image by file name, so the two belong in the same folder.

.json — the same data in a shape that is easier to read in a text editor or feed to your own loader.

The preview as PNG — the text you typed, set with the font, at its native size. Useful for a title, a screenshot, or checking the spacing outside the browser.

Loading it in your engine

Every one of these wants the .fnt and the PNG together.

Phaser 3

this.load.bitmapFont(key, 'font.png', 'font.fnt'), then this.add.bitmapText(x, y, key, text). Add pixelArt: true to the game config.

LibGDX

new BitmapFont(Gdx.files.internal("font.fnt")). Set the texture filter to Nearest on both min and mag, or the glyphs blur when the camera is not at 1:1.

love2d

love.graphics.newFont("font.fnt") reads BMFont directly. Call setFilter("nearest", "nearest") on the font's image.

Frequently asked questions

What is a .fnt file?

AngelCode's BMFont format: a text file listing, for every character, which rectangle of the image holds it and how far the pen should move afterwards. The image and the .fnt travel together — the .fnt names the image, so keep the two side by side. Phaser, LibGDX, love2d and most other bitmap-font loaders read it.

How does it know where one character ends and the next begins?

Automatic mode floods islands of solid pixels, the same way the sprite sheet cutter finds frames. The font-specific part is what happens next: a glyph is often more than one island — the dot of an i, the two dots of a colon, the bar of an exclamation mark — so islands are joined when one sits horizontally inside the other. Joining by distance instead, which is what a sprite sheet does, would just as happily join two neighbouring letters.

My preview reads as gibberish. What went wrong?

The character string is out of step with the sheet, almost always by one. The first box takes the first character, the second the second, and if the sheet starts with a space or has a stray pixel somewhere, everything after it shifts. The tool reports boxes with no character and characters with no box, which is usually enough to find where the drift starts.

Does it handle variable-width fonts?

That is the default. Each glyph advances by its own measured width plus the tracking, so an I takes less room than a W. In grid mode the cells are trimmed to their ink first, so even a fixed grid produces a proportional font — set tracking to make it monospace if that is what you want.

What about the space character?

A space has no pixels, so nothing can detect it. It gets a width you set instead, and the default is half the widest glyph, which is close to right for most pixel faces.

Can I use it for a font that is not Latin?

Yes. The character string is just text, so put any characters in it in the order they appear on the sheet, and each glyph is written out with its code point. Whether your engine renders them depends on the engine, not the file.

Are my images uploaded to a server?

No. The sheet is decoded, sliced and set with the Canvas API inside your browser. Nothing leaves your machine, and the tool keeps working offline once the page has loaded.

Related guides

Related tools