
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 MapfontsByName; public GeneratedFontSkinLoader(FileHandleResolver resolver, MapfontsByName ) { 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; ObjectMapresources; 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.Entrykv : this.fontsByName.entrySet() ) { skin.add( kv.getKey(), kv.getValue() ); } if (resources != null) { for (Entryentry : 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 MapinitFonts() { Gdx.app.log( "INIT", "Loading fonts..." ); FileHandle fontFile = Gdx.files.internal("data/comic-sans.ttf"); FreeTypeFontGenerator generator = new FreeTypeFontGenerator(fontFile); MapfontsByName = new HashMap generator.dispose(); return fontsByName; }(); 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 ));
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() { MapfontsByName = 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 } }