ExcaliBug

Why Godot draws your variable font thin and your Japanese bold

2 min readgodotfontslocalization

While building PDF Writer, I set the demo's variable Nunito to weight 500 and added a page of names in eleven scripts. The Latin looked right. The Japanese came out bold. Labels do the same thing, because they lay out text with the same engine.

There are two traps here, and fixing the first one walks you into the second.

1. The font file on its own is the lightest weight

A variable font holds every weight in one file. Load the .ttf straight into a Label and Godot uses the file's default instance, which isn't always Regular. For Nunito it's ExtraLight: TextServer.font_get_weight() reports 200 for it, and your UI text comes out thin.

The fix is to wrap the file in a FontVariation and pick the weight. In the editor, that's a new FontVariation resource with the weight set under Variation › OpenType. In code:

var regular := FontVariation.new()
regular.base_font = preload("res://fonts/Nunito-Variable.ttf")
var wght := TextServerManager.get_primary_interface().name_to_tag("wght")
regular.variation_opentype = {wght: 500}

2. Text your font can't draw comes out heavier

Nunito has no Japanese, so Godot finds a system font that does. It looks for one near your weight, but the face it picks lands a step heavier. This is Godot 4.7.2 on Windows, where the Japanese system font is Yu Gothic:

  • Nunito file as is (200): Japanese in Yu Gothic Light (300)
  • Nunito at 400: Yu Gothic Medium (500)
  • Nunito at 500: Yu Gothic Bold (700)
  • Nunito at 600: Yu Gothic Bold (700)

So at 500 your Latin is medium and your Japanese is bold. Other scripts depend on the fonts installed: Korean stayed in Malgun Gothic Regular in the same test.

My first fix didn't work: I added a SystemFont for "sans-serif" at weight 500 to the font's fallbacks. On Windows that resolves to Arial, which has no Japanese either, so Godot falls back from it the same way and still picks Bold.

The fix: name the fallback font

Fallbacks only help when they can draw the text themselves. Name the fonts that cover the script, at the weight you want:

var japanese := SystemFont.new()
japanese.font_names = PackedStringArray(["Yu Gothic", "Hiragino Sans", "Noto Sans CJK JP"])
japanese.font_weight = 500
regular.fallbacks = [japanese]

That gives Yu Gothic Medium, a match for the Latin. Godot uses the first name the system has. I tested this on Windows; Hiragino Sans and Noto Sans CJK JP are the usual Japanese fonts on macOS and Linux.

For a game you ship, go one step further and bundle a font for each script you support, each in a FontVariation at the same weight as your main font, in the same fallbacks list. Then the weights match on every platform, and web exports, which have no system fonts at all, still draw the text.

PDF Writer runs into both of these, because it prints whatever font your game uses. It gives text your font can't draw a system font of its own at the regular or bold weight, so a weight-500 page doesn't print bold Japanese.