DEV Community

FrameSprite for FrameSprite

Posted on Originally published at framesprite.com Fully Autonomous

Bitmap Font Spacing: Advance Is Not Glyph Width

Disclosure: This article was generated with AI for the FrameSprite team, which has a commercial interest in FrameSprite. It received a separate AI editorial review against the cited sources. Executed synthetic fixtures are identified with their runtime and limits; no other editor, engine, production or performance test is implied.

Advance the text cursor with xadvance, not with the width of the visible glyph. The bitmap rectangle describes the pixels to draw. The advance describes where the next character starts. A space may have no bitmap at all and still move the cursor.

Key takeaway: A tightly packed atlas can preserve generous letter spacing. Repacking the texture does not justify changing layout metrics.

Catch the zero-width space case first

A useful failure sample is A, a space, then B. It exposes width-based cursor movement without needing a whole paragraph. The BMFont format reference gives width and xadvance separate meanings.

This authored sample deliberately uses different values:

Character Visible width Advance Correct cursor x before drawing
A 6 8 0
Space 0 4 8
B 7 8 12

The final cursor is 20. Summing only visible widths puts the cursor at 0, 6 and 6, finishing at 13. That is why B can collide with the preceding material even when every atlas rectangle is correct.

Keep drawing and movement as two operations

First position the glyph using its offset. Then move the cursor by its advance. The BMFont rendering explanation makes this distinction explicit; neither xoffset nor glyph width is a substitute for cursor movement.

widths = [6, 0, 7]
advances = [8, 4, 8]
cursor_x = 0
starts = []
for width, advance in zip(widths, advances):
    starts.append(cursor_x)
    # Draw this glyph at cursor_x + its xoffset.
    # A zero-width glyph may have nothing to draw.
    cursor_x += advance
assert starts == [0, 8, 12]
assert cursor_x == 20
Enter fullscreen mode Exit fullscreen mode

Store advance even when you skip drawing an empty glyph. Do not remove the space entry as an optimization unless the layout code has another explicit rule for it. Likewise, do not replace a negative glyph bearing with extra advance: the bearing places the ink, while the advance places the next cursor.

Verify the data before blaming the packer

The downloadable input and executed result compare the correct positions with the width-only negative control. Python 3.12.10 ran three assertions in the checker. The result concerns authored integer metrics, not an engine rendering test or a measurement of a real font.

When diagnosing an actual export, log character ID, bitmap width, xoffset, xadvance and final destination x. If the descriptor already contains correct advances, repair the consumer. If the descriptor has lost them, return to the export step and retain the original metrics. Adding padding to the PNG will not repair an importer that ignores advance values.

The Cyotek consumer documentation describes combining advance with kerning. Add that second correction only after the basic advance path works, so one error does not hide another.

Route similar symptoms to the right check

Use the font coverage guide when a character has no valid glyph entry. Use the grid SpriteFont map guide when the font is a fixed cell strip rather than a proportional descriptor. If a runtime-generated label atlas grows unexpectedly, check Bitmap Label usage before expanding the source texture.

Where this repair stops

Correct advances do not implement kerning, bidirectional layout or ligatures. This guide handles one linear glyph sequence. Keep an explicit text-shaping stage for scripts or typography that need it, and preserve the source font's rights when distributing the atlas.

Editorial ownership

Prepared by the workflow team, which has a commercial relationship with this site. This guide was drafted with AI assistance; independent editorial review is recorded separately. Report a technical correction with the relevant input, tool version and observed result.

Original guide and downloadable materials

Top comments (0)