Libgdx freetype
Claude Code skills for working with the libGDX framework
npx -y skills add kyu-n/gdx-claude-skills --skill libgdx-freetypeAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 4 stars4 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Use when writing libGDX Java/Kotlin code that generates BitmapFont at runtime from .ttf/.otf files using gdx-freetype — FreeTypeFontGenerator, FreeTypeFontParameter, font size/color/border/shadow configuration, characters parameter for i18n, incremental glyph rendering for CJK, AssetManager integration with FreeTypeFontLoader, or density-scaled font sizes. Use when debugging missing glyphs, blurry fonts, generator disposal issues, or FreeType crashes on iOS.
SKILL.md
10.5 KB, as published. Nobody here has run it
libGDX gdx-freetype
Runtime BitmapFont generation from .ttf/.otf files. Use instead of pre-baking fonts with Hiero when you need multiple sizes, dynamic scaling, or user-selectable fonts.
Gradle Dependencies
// core
implementation "com.badlogicgames.gdx:gdx-freetype:$gdxVersion"
// desktop
implementation "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-desktop"
// android (all four ABIs)
natives "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-armeabi-v7a"
natives "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-arm64-v8a"
natives "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-x86"
natives "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-x86_64"
// iOS
natives "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-ios"
The platform artifact is gdx-freetype-platform, not gdx-freetype. Missing platform natives causes UnsatisfiedLinkError at runtime.
iOS/RoboVM: Also add to robovm.xml inside <config>:
<forceLinkClasses>
<pattern>com.badlogic.gdx.graphics.g2d.freetype.**</pattern>
</forceLinkClasses>
Without this, RoboVM's AOT compiler strips FreeType classes, causing runtime crashes on iOS.
Basic Usage
FreeTypeFontGenerator generator = new FreeTypeFontGenerator(Gdx.files.internal("fonts/myfont.ttf"));
FreeTypeFontParameter parameter = new FreeTypeFontParameter();
parameter.size = (int)(24 * Gdx.graphics.getDensity()); // density-scaled
parameter.color = Color.WHITE;
parameter.borderWidth = 1;
parameter.borderColor = Color.BLACK;
parameter.minFilter = Texture.TextureFilter.Linear;
parameter.magFilter = Texture.TextureFilter.Linear;
parameter.characters = FreeTypeFontGenerator.DEFAULT_CHARS; // ASCII + common punctuation
BitmapFont font = generator.generateFont(parameter);
generator.dispose(); // safe — the font is independent of the generator
// font remains fully usable after generator disposal
FreeType requires an OpenGL context. It bakes glyphs into a texture atlas. It does not work in the headless backend or before create().
FreeTypeFontGenerator is NOT thread-safe. Generating fonts from multiple threads simultaneously crashes. AssetManager handles synchronization internally, but manual multi-threaded generation will fail.
Key Parameter Fields
| Field | Default | Description |
|---|---|---|
size | 16 | Pixel height. Scale with Gdx.graphics.getDensity() for consistent physical size across devices. |
characters | DEFAULT_CHARS | Which glyphs to bake. ASCII by default — add accented chars for European languages, or use incremental for CJK. |
color | Color.WHITE | Font fill color |
borderWidth | 0 | Outline thickness in pixels |
borderColor | Color.BLACK | Outline color |
shadowOffsetX/Y | 0 | Drop shadow offset |
shadowColor | Color(0,0,0,0.75f) | Drop shadow color |
minFilter / magFilter | Nearest | Texture filtering. Use Linear for smooth scaled text. |
hinting | AutoMedium | None, Slight, Medium, Full, AutoSlight, AutoMedium, AutoFull. Default is best for most fonts. Use None for pixel art fonts; Full can cause artifacts with some fonts. |
kerning | true | Adjust spacing between glyph pairs |
mono | false | Monospace rendering (all glyphs same width) |
genMipMaps | false | Generate mipmaps for minification |
incremental | false | Render glyphs on demand instead of all at once — see CJK section |
Disposal Lifecycle
Generator and font are independent. After generateFont(), the font owns its own texture. The generator is no longer needed.
// Generate multiple sizes from one .ttf, then dispose generator
FreeTypeFontGenerator generator = new FreeTypeFontGenerator(Gdx.files.internal("font.ttf"));
FreeTypeFontParameter param = new FreeTypeFontParameter();
param.size = 16;
BitmapFont small = generator.generateFont(param);
param.size = 24;
BitmapFont medium = generator.generateFont(param);
param.size = 48;
BitmapFont large = generator.generateFont(param);
generator.dispose(); // fonts remain usable
// In dispose():
small.dispose();
medium.dispose();
large.dispose();
Exception — incremental mode: When parameter.incremental = true, the generator must stay alive because it renders new glyphs on demand. Dispose the generator only after disposing the font. See CJK section below.
Generated BitmapFonts are managed textures — they auto-reload after Android GL context loss like any other managed resource. Do NOT re-run the generator in resume() to "reload" fonts.
Characters Parameter (i18n)
DEFAULT_CHARS covers basic ASCII. For European languages, add accented characters:
parameter.characters = FreeTypeFontGenerator.DEFAULT_CHARS
+ "ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ"
+ "ŁłŃńŚśŹźŻżŒœ¿¡«»";
Characters not in the set render as missing glyphs. If text looks broken, check this parameter first.
Atlas size: The default atlas page is 1024x1024. Large character sets without incremental mode can fill it, silently dropping glyphs. For large sets, either use incremental = true or provide a larger packer: parameter.packer = new PixmapPacker(2048, 2048, Pixmap.Format.RGBA8888, 1, false);
Incremental Rendering (CJK)
CJK character sets have thousands of glyphs. Baking them all upfront creates enormous textures and long load times. Use incremental mode instead:
FreeTypeFontGenerator generator = new FreeTypeFontGenerator(Gdx.files.internal("fonts/NotoSansCJK.ttf"));
FreeTypeFontParameter param = new FreeTypeFontParameter();
param.size = 24;
param.incremental = true; // glyphs rendered on first use
param.characters = ""; // start empty — glyphs added on demand
BitmapFont cjkFont = generator.generateFont(param);
// DO NOT dispose generator — it's needed to render new glyphs
With incremental = true:
- Glyphs are baked into the atlas the first time they're drawn
- The generator MUST stay alive — dispose it only after the font
- Small hitch on first render of each new glyph
- Pre-load common characters during a loading screen to reduce hitches
// Dispose order for incremental fonts
cjkFont.dispose();
generator.dispose(); // AFTER the font
AssetManager Integration
Register the FreeType loaders, then load fonts asynchronously:
AssetManager assets = new AssetManager();
// Register loaders (once, at startup)
assets.setLoader(FreeTypeFontGenerator.class,
new FreeTypeFontGeneratorLoader(assets.getFileHandleResolver()));
assets.setLoader(BitmapFont.class, ".ttf",
new FreetypeFontLoader(assets.getFileHandleResolver()));
// Queue a font for loading
FreetypeFontLoader.FreeTypeFontLoaderParameter params =
new FreetypeFontLoader.FreeTypeFontLoaderParameter();
params.fontFileName = "fonts/myfont.ttf"; // actual .ttf path
params.fontParameters.size = (int)(24 * Gdx.graphics.getDensity());
params.fontParameters.minFilter = Texture.TextureFilter.Linear;
params.fontParameters.magFilter = Texture.TextureFilter.Linear;
assets.load("myfont-24.ttf", BitmapFont.class, params); // key is arbitrary, not a file path
// In render loop:
if (assets.update()) {
BitmapFont font = assets.get("myfont-24.ttf", BitmapFont.class);
}
The asset key (first argument to load()) doesn't need to match the actual .ttf filename, but it must end in .ttf — the FreeType loader is registered for the .ttf extension. A key like "heading-font" without .ttf won't match the loader and silently fails to load.
Density Scaling
Scale font size by screen density so text appears the same physical size across devices:
parameter.size = (int)(24 * Gdx.graphics.getDensity());
// mdpi (1.0) → 24px, xhdpi (2.0) → 48px, xxhdpi (3.0) → 72px
Common Mistakes
- Wrong platform artifact name — The natives artifact is
gdx-freetype-platform, notgdx-freetype. Using the wrong name compiles fine but crashes withUnsatisfiedLinkErrorat runtime. - Disposing generator before generating the font — The generator must be alive when
generateFont()is called. Dispose it after. - Thinking font disposal is tied to generator disposal — They're independent. Disposing the generator does NOT affect already-generated fonts (unless using incremental mode).
- Disposing generator in incremental mode — When
incremental = true, the generator must stay alive to render new glyphs on demand. Dispose the generator only after the font. - Missing
charactersfor i18n —DEFAULT_CHARSis ASCII only. European accented characters (é, ñ, ü) must be added explicitly or they render as missing glyphs. - Not knowing about
incrementalfor CJK — Baking all CJK glyphs upfront creates huge textures. Useparameter.incremental = truefor on-demand rendering. - Suggesting Hiero when runtime generation is needed — Hiero pre-bakes fonts at build time. Use FreeType when sizes must be computed at runtime (density scaling, user preferences).
- Missing platform natives in Gradle — The core
gdx-freetypedependency alone is not enough. Each platform needs itsgdx-freetype-platformnatives or FreeType crashes at runtime. - Missing robovm.xml forceLinkClasses for iOS — RoboVM strips FreeType classes without this entry. App compiles but crashes on iOS.
- Hardcoding pixel size without density scaling —
parameter.size = 24looks different on every device. Use(int)(24 * Gdx.graphics.getDensity())for consistent physical size. - Using FreeType in headless backend — FreeType bakes glyphs into a GL texture atlas. It requires an OpenGL context and does not work in headless mode.
- Re-running generator in
resume()on Android — Generated BitmapFonts are managed textures and auto-reload after GL context loss. Regenerating wastes time and leaks the old font. - AssetManager key without
.ttfextension — The FreeType loader is registered for.ttf. A key like"heading-font"won't match — use"heading-font.ttf"instead.