Get the FREE Ultimate OpenClaw Setup Guide →

libgdx-freetype

npx machina-cli add skill kyu-n/gdx-claude-skills/libgdx-freetype --openclaw
Files (1)
SKILL.md
10.5 KB

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

FieldDefaultDescription
size16Pixel height. Scale with Gdx.graphics.getDensity() for consistent physical size across devices.
charactersDEFAULT_CHARSWhich glyphs to bake. ASCII by default — add accented chars for European languages, or use incremental for CJK.
colorColor.WHITEFont fill color
borderWidth0Outline thickness in pixels
borderColorColor.BLACKOutline color
shadowOffsetX/Y0Drop shadow offset
shadowColorColor(0,0,0,0.75f)Drop shadow color
minFilter / magFilterNearestTexture filtering. Use Linear for smooth scaled text.
hintingAutoMediumNone, 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.
kerningtrueAdjust spacing between glyph pairs
monofalseMonospace rendering (all glyphs same width)
genMipMapsfalseGenerate mipmaps for minification
incrementalfalseRender 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

  1. Wrong platform artifact name — The natives artifact is gdx-freetype-platform, not gdx-freetype. Using the wrong name compiles fine but crashes with UnsatisfiedLinkError at runtime.
  2. Disposing generator before generating the font — The generator must be alive when generateFont() is called. Dispose it after.
  3. Thinking font disposal is tied to generator disposal — They're independent. Disposing the generator does NOT affect already-generated fonts (unless using incremental mode).
  4. 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.
  5. Missing characters for i18nDEFAULT_CHARS is ASCII only. European accented characters (é, ñ, ü) must be added explicitly or they render as missing glyphs.
  6. Not knowing about incremental for CJK — Baking all CJK glyphs upfront creates huge textures. Use parameter.incremental = true for on-demand rendering.
  7. 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).
  8. Missing platform natives in Gradle — The core gdx-freetype dependency alone is not enough. Each platform needs its gdx-freetype-platform natives or FreeType crashes at runtime.
  9. Missing robovm.xml forceLinkClasses for iOS — RoboVM strips FreeType classes without this entry. App compiles but crashes on iOS.
  10. Hardcoding pixel size without density scalingparameter.size = 24 looks different on every device. Use (int)(24 * Gdx.graphics.getDensity()) for consistent physical size.
  11. 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.
  12. 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.
  13. AssetManager key without .ttf extension — The FreeType loader is registered for .ttf. A key like "heading-font" won't match — use "heading-font.ttf" instead.

Source

git clone https://github.com/kyu-n/gdx-claude-skills/blob/master/skills/libgdx-freetype/SKILL.mdView on GitHub

Overview

libgdx-freetype enables BitmapFont generation at runtime from .ttf/.otf files using FreeTypeFontGenerator. It supports density-scaled sizes, color, borders, shadows, and i18n glyphs, with incremental glyph rendering for CJK and AssetManager integration via FreeTypeFontLoader. This is essential for multiple sizes, dynamic font selection, and debugging font issues, especially on iOS.

How This Skill Works

Create a FreeTypeFontGenerator from a font file, configure a FreeTypeFontParameter (size, color, border/shadow, characters, etc.), and call generateFont to bake a BitmapFont that uses a texture atlas. After generation, dispose the generator safely while the font remains usable; note that FreeType requires an OpenGL context and is not thread-safe, so use AssetManager for synchronized loading when possible.

When to Use It

  • Need multiple font sizes or user-selected fonts at runtime
  • Require extended glyphs (i18n) or CJK support with on-demand rendering
  • Debugging missing glyphs, blurry fonts, or FreeType issues on iOS
  • Integrating font loading with AssetManager via FreeTypeFontLoader
  • Maintaining density-consistent font sizes across devices

Quick Start

  1. Step 1: FreeTypeFontGenerator generator = new FreeTypeFontGenerator(Gdx.files.internal("fonts/myfont.ttf"));
  2. Step 2: FreeTypeFontParameter parameter = new FreeTypeFontParameter(); parameter.size = (int)(24 * Gdx.graphics.getDensity()); parameter.color = Color.WHITE; parameter.characters = FreeTypeFontGenerator.DEFAULT_CHARS;
  3. Step 3: BitmapFont font = generator.generateFont(parameter); generator.dispose();

Best Practices

  • Scale font sizes with device density: parameter.size = (int)(desired * Gdx.graphics.getDensity())
  • Specify characters to bake (default ASCII plus accents); enable incremental for CJK if needed
  • Tune texture filtering and hinting (minFilter/magFilter, hinting) for readability
  • Dispose the FreeTypeFontGenerator after generateFont; the BitmapFont remains usable
  • For iOS/ RoboVM, include platform natives and forceLinkClasses to prevent crashes

Example Use Cases

  • Desktop UI with multiple font sizes for labels and menus
  • Mobile game with density-scaled fonts and outline/border effects
  • Internationalized game supporting accented European languages
  • CJK-heavy text rendering using incremental glyph loading
  • Asynchronous font loading via AssetManager and FreeTypeFontLoader

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers