Missing Glyph Checker — Localization Text vs Bitmap Font
Before a Korean or Japanese build ships boxes instead of letters: compare the text in your localization files with the glyphs a BMFont actually contains.
Boxes (□) or blank gaps where Korean or Japanese letters should be mean that the font has no glyph for those code points. A bitmap font contains only the characters baked into it, one char id= line per glyph in its .fnt. The checker pulls the translated text out of your .po (the msgstr values), .json, .csv/.tsv or .txt, compares every distinct character with the .fnt (or the font built in the UI Lab's Font stage), and lists each missing character with its Unicode code point, how often it is used and the first lines it appears on. It cannot read a TTF's character map yet.

- Nine-sliceMeasured· Pillow
- Bitmap fontMeasured· independent .fnt parser
- Button statesMeasured· Pillow
- Engine importUNVERIFIED
Why letters turn into boxes
Every character is a Unicode code point, and a BMFont stores it in decimal: 시 is U+C2DC, which the .fnt writes as char id=49884. The check is plain set membership: the distinct characters of the text minus the char id values of the font. Scripts are large, which is why game fonts for Korean or Japanese are built from the strings the game actually uses: the precomposed Hangul syllables U+AC00–U+D7A3 are 11,172 code points, and the CJK Unified Ideographs block U+4E00–U+9FFF spans 20,992.
The usual causes are a translation updated after the font was baked; a font built from the source-language text; look-alike characters with different code points (full-width ! U+FF01 against ! U+0021); and decomposed Hangul, where 가 arrives as the jamo U+1100 U+1161 instead of the single syllable U+AC00. The checker compares code points exactly, so it reports all of these.
What is read from each file matters. From a .po only msgstr (and msgstr[n]) values are taken, never the msgid source keys, but the header entry's msgstr (Language:, Content-Type:) counts as text too. JSON contributes every string value, CSV and TSV every cell including the key and source columns. Whitespace, including the no-break space U+00A0 and the ideographic space U+3000, is skipped because it is not drawn as a glyph.
Example: a Korean .po against a 4-glyph .fnt
The font was baked from 시, 작, 끝 and 내. The report below is what the checker returns for this file:
ko.po (9 lines) font.fnt (4 glyphs)
msgid "" char id=49884 시 U+C2DC
msgstr "" char id=51089 작 U+C791
"Language: ko\n" char id=45149 끝 U+B05D
msgid "Start" → msgstr "시작" char id=45236 내 U+B0B4
msgid "Quit" → msgstr "끝내기"
text checked "Language: ko" / "시작" / "끝내기" (msgid values are not read)
missing 기 U+AE30 ×1 (text line 5)
a ×2, g ×2, :, L, e, k, n, o, u (header entry, text line 2)
to add 기 = 0xAE30 = 44592 → the rebuilt font needs "char id=44592"The Latin letters come from the header entry, not from a translation. Either add ASCII to the font (most games need digits and Latin letters anyway) or ignore those rows; the real gap here is 기.
Where the checked text comes from
| File | What is read | What is ignored |
|---|---|---|
.po | msgstr and msgstr[n] values with their continuation lines (the header entry included) | msgid, msgctxt, comments |
.json | Every string value at any depth | Object keys, numbers, booleans |
.csv / .tsv | Every cell of every row, header row and key column included | Separators and quotes |
.txt or pasted text | Everything | Line breaks and other whitespace |
.fnt (glyph source) | The id of every char line | Kerning, pages, metrics |
What it does
- Reads .po (msgstr values), JSON (all string values), CSV/TSV (all cells) and plain text.
- Compares with a .fnt's glyph list or the font made in the Lab's Font stage.
- Every missing character with its count and the first lines it appears on.
- Downloads missing-glyphs.json and a plain list to paste into a font tool.
How to do it
- Choose the glyph source (a .fnt or the Lab's font).
- Drop the localization file or paste text.
- Read the missing list.
- Export the report.
Check the result
- After adding the characters and rebuilding the font, run the check again: it should say that nothing is missing and name how many distinct characters were checked.
- Open
missing-glyphs.json: every entry haschar,codepointasU+XXXX,countandlines;missing-glyphs.txtis the same characters on one line, ready to paste into a font tool's character field. - In the game, show one test string that contains every character of the language file: no box and no blank should remain.
Troubleshooting
| Symptom | Likely cause | How to check | Fix |
|---|---|---|---|
| Latin letters (L, a, n, g…) are reported for a Korean or Japanese .po | The header entry's msgstr (Language:, Content-Type:) is read as text | The listed lines point at the first lines of the extracted text | Add ASCII to the font, or ignore those rows |
| The English source column of a CSV is reported | Every CSV/TSV cell is read, including key and source columns | Most missing characters are Latin and appear on every line | Export only the target-language column, or paste its text instead |
| A character that looks present is reported | A look-alike with another code point (full-width !, ~, a different dash) | Compare the U+ code in the report with the .fnt ids | Add that exact code point to the font, or normalise the text before export |
| Hangul syllables are reported although the font has them | The text is decomposed: jamo U+1100–U+11FF instead of syllables | The report lists U+11xx codes | Normalise the strings to NFC in your localisation pipeline |
| Nothing is missing but the game still shows boxes | The game draws with another font or weight than the checked .fnt, or the UI Lab font was built in font-file mode, where a character missing from the TTF is rendered by the browser's fallback font and baked in | Check the .fnt the build actually ships; look at the glyph sheet for letters in a different style | Check against the shipped .fnt; in Godot a BMFont can list fallback fonts in its importer |
| The line numbers do not match the file | Lines are counted in the extracted text, not in the original file | Line 2 of a .po report is the header text | Search the file for the character itself |
| A TTF or OTF cannot be chosen as the glyph source | The font's cmap table is not parsed in this release | — | Build a .fnt from the font first, then check against it |
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
- 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 .po checked against a 4-glyph .fnt reported exactly a 시 작 끝 내 기 with the right counts and line numbers, and the present 가 was not reported. The worked example above was recomputed with the same functions; its header-entry rows are how the current parser behaves. The .fnt field meanings follow the AngelCode BMFont documentation, the .po structure the GNU gettext manual.
Official documentation used
Questions
Can I check against a TTF font?
Not yet: a TTF's cmap table is not parsed in this release, so use a .fnt or the Lab's own font.
Was it checked on real files?
A .po file against a 4-glyph .fnt reported exactly the 6 missing characters with the right counts and line numbers.
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).