Generated Fonts in LibGDX Skins

Generated Fonts in LibGDX Skins

Font scaling in a messy business and LibGDX is no exception. It can take a lot of effort to get a crisp rendering. There are a number of strategies for getting the result you want, and utilities like Hiero are a big help. It still makes for a lot of fussing with fonts, time better spent elsewhere.

LibGDX can genreate bitmap fonts at runtime, which side-steps the scaling and interpolation altogether. Load the font, set the size and voilĂ , your font just how you like it. Unfortunately, these generated fonts do not work with Scene2D UI Skins. Building a complex UI without Skins poses a severe risk to one’s mental health. You have been warned.

Skins describe style information including colour, background images, fonts, and all the other bits and pieces required to build an interface. They are defined in a JSON file and the SkinLoader class deserializes it into Scene2d classes. The loader expects any fonts mentioned in the JSON to exist in the filesystem. SkinLoader does not use the AssetManager, so a generated font added to an AssetManager will not be used.

In order to get generated bitmap fonts working with Skins, a new SkinLoader is required that will know where to find generated fonts.

public class GeneratedFontSkinLoader extends SkinLoader {

private Map fontsByName;

public GeneratedFontSkinLoader(FileHandleResolver resolver, Map fontsByName ) {
super(resolver); 

if( fontsByName == null ) {
throw new NullPointerException( "Font map is null." );
}

this.fontsByName = fontsByName;
}

@Override
public Skin loadSync(AssetManager manager, String fileName, FileHandle file, SkinParameter parameter) {

String textureAtlasPath;
ObjectMap resources;

if (parameter == null) {
textureAtlasPath = file.pathWithoutExtension() + ".atlas";
resources = null;
} else {
textureAtlasPath = parameter.textureAtlasPath;
resources = parameter.resources;
}

TextureAtlas atlas = manager.get(textureAtlasPath, TextureAtlas.class);
Skin skin = new Skin(atlas);

for( Map.Entry kv : this.fontsByName.entrySet() ) {

skin.add( kv.getKey(), kv.getValue() );
}

if (resources != null) {
for (Entry entry : resources.entries()) {
skin.add(entry.key, entry.value);
}
}
skin.load(file);
return skin;
}
}

I lifted the guts of loadSync directly from the LibGDX implementation, SkinLoader. The only difference is force-feeding the generated fonts into the Skin on lines 32-35. The rest of the deserialization process remains unchanged.

The map of generated fonts can be built using a method similar to the following, and then passed along the the constructor of the GeneratedFontSkinLoader.

protected Map  initFonts() {

Gdx.app.log( "INIT", "Loading fonts..." );

FileHandle fontFile = Gdx.files.internal("data/comic-sans.ttf");
FreeTypeFontGenerator generator = new FreeTypeFontGenerator(fontFile);

Map fontsByName = new HashMap();
FreeTypeFontParameter param = new FreeTypeFontParameter();
float ppi = Gdx.graphics.getPpiY();
param.size = Math.round( ppi / 2);
fontsByName.put( "huge-font", generator.generateFont( param ));
param.size = Math.round( ppi / 3);
fontsByName.put( "big-font", generator.generateFont( param ));
param.size = Math.round( ppi / 4);
fontsByName.put( "small-font", generator.generateFont( param ));

generator.dispose();
return fontsByName;
}

Finally, tell the AssetManager to use the new loader to handle Skin files. Putting it all together, you get something like this:

@Override
public void show() {

Map fontsByName = initFonts(); 

SkinLoader ldr = new GeneratedFontSkinLoader( new InternalFileHandleResolver(), this.fontsByName);

this.assetMgr.setLoader( Skin.class, ldr );

this.assetMgr.load( "data/uiskin.atlas", TextureAtlas.class );
this.assetMgr.load( "data/uiskin.json", Skin.class );

// etc...
}

The fonts can then be safely used in the JSON file. Just make sure the names line up with the keys used in initFonts.

com.badlogic.gdx.scenes.scene2d.ui.Label$LabelStyle: {
default: { font: big-font, fontColor: red, font: big-font }
huge: { font: big-font, fontColor: red, font: huge-font }
small: { font: big-font, fontColor: red, font: small-font }
}