{"id":359,"date":"2014-06-15T14:14:57","date_gmt":"2014-06-15T18:14:57","guid":{"rendered":"http:\/\/pmcgovern.ca\/wp\/?p=359"},"modified":"2021-12-12T12:47:18","modified_gmt":"2021-12-12T17:47:18","slug":"generated-fonts-in-libgdx-skins","status":"publish","type":"post","link":"https:\/\/pmcgovern.ca\/wp\/?p=359","title":{"rendered":"Generated Fonts in LibGDX Skins"},"content":{"rendered":"<p>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 <a href=\"http:\/\/stackoverflow.com\/questions\/14675007\/how-to-draw-smooth-text-in-libgdx\">strategies<\/a> for getting the result you want, and utilities like <a href=\"https:\/\/github.com\/libgdx\/libgdx\/wiki\/Hiero\">Hiero<\/a> are a big help. It still makes for a lot of fussing with fonts, time better spent elsewhere.<\/p>\n<p>LibGDX can genreate bitmap fonts at runtime, which side-steps the scaling and interpolation altogether. Load the font, set the size and voil\u00e0, your font just how you like it. Unfortunately, these generated fonts do not work with <a href=\"https:\/\/github.com\/libgdx\/libgdx\/wiki\/Scene2d.ui\">Scene2D UI<\/a> <a href=\"https:\/\/github.com\/libgdx\/libgdx\/wiki\/Skin\">Skins<\/a>. Building a complex UI without Skins poses a <em>severe<\/em> risk to one&#8217;s mental health. You have been warned.<\/p>\n<p>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 <a href=\"http:\/\/libgdx.badlogicgames.com\/nightlies\/docs\/api\/com\/badlogic\/gdx\/assets\/loaders\/SkinLoader.html\">SkinLoader<\/a> 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 <a href=\"http:\/\/libgdx.badlogicgames.com\/nightlies\/docs\/api\/com\/badlogic\/gdx\/assets\/AssetManager.html\">AssetManager<\/a>, so a generated font added to an AssetManager will not be used.<\/p>\n<p>In order to get generated bitmap fonts working with Skins, a new SkinLoader is required that will know where to find generated fonts.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\">public class GeneratedFontSkinLoader extends SkinLoader {\n\nprivate Map<string,bitmapfont> fontsByName;<\/string,bitmapfont>\n\npublic GeneratedFontSkinLoader(FileHandleResolver resolver, Map<string,bitmapfont> fontsByName ) {\nsuper(resolver); <\/string,bitmapfont>\n\nif( fontsByName == null ) {\nthrow new NullPointerException( \"Font map is null.\" );\n}\n\nthis.fontsByName = fontsByName;\n}\n\n@Override\npublic Skin loadSync(AssetManager manager, String fileName, FileHandle file, SkinParameter parameter) {\n\nString textureAtlasPath;\nObjectMap<string, object=\"\"> resources;<\/string,>\n\nif (parameter == null) {\ntextureAtlasPath = file.pathWithoutExtension() + \".atlas\";\nresources = null;\n} else {\ntextureAtlasPath = parameter.textureAtlasPath;\nresources = parameter.resources;\n}\n\nTextureAtlas atlas = manager.get(textureAtlasPath, TextureAtlas.class);\nSkin skin = new Skin(atlas);\n\nfor( Map.Entry<string,bitmapfont> kv : this.fontsByName.entrySet() ) {<\/string,bitmapfont>\n\nskin.add( kv.getKey(), kv.getValue() );\n}\n\nif (resources != null) {\nfor (Entry<string, object=\"\"> entry : resources.entries()) {\nskin.add(entry.key, entry.value);\n}\n}\nskin.load(file);\nreturn skin;\n}\n}\n<\/string,><\/pre>\n<p>I lifted the guts of <strong>loadSync<\/strong> directly from the LibGDX implementation, <a href=\"https:\/\/github.com\/libgdx\/libgdx\/blob\/master\/gdx\/src\/com\/badlogic\/gdx\/assets\/loaders\/SkinLoader.java\">SkinLoader<\/a>. The only difference is force-feeding the generated fonts into the Skin on lines 32-35. The rest of the deserialization process remains unchanged.<\/p>\n<p>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.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\">protected Map<string,bitmapfont>  initFonts() {<\/string,bitmapfont>\n\nGdx.app.log( \"INIT\", \"Loading fonts...\" );\n\nFileHandle fontFile = Gdx.files.internal(\"data\/comic-sans.ttf\");\nFreeTypeFontGenerator generator = new FreeTypeFontGenerator(fontFile);\n\nMap<string,bitmapfont> fontsByName = new HashMap<string,bitmapfont>();\nFreeTypeFontParameter param = new FreeTypeFontParameter();\nfloat ppi = Gdx.graphics.getPpiY();\nparam.size = Math.round( ppi \/ 2);\nfontsByName.put( \"huge-font\", generator.generateFont( param ));\nparam.size = Math.round( ppi \/ 3);\nfontsByName.put( \"big-font\", generator.generateFont( param ));\nparam.size = Math.round( ppi \/ 4);\nfontsByName.put( \"small-font\", generator.generateFont( param ));<\/string,bitmapfont><\/string,bitmapfont>\n\ngenerator.dispose();\nreturn fontsByName;\n}\n<\/pre>\n<p>Finally, tell the AssetManager to use the new loader to handle Skin files. Putting it all together, you get something like this:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\">@Override\npublic void show() {\n\nMap<string,bitmapfont> fontsByName = initFonts(); <\/string,bitmapfont>\n\nSkinLoader ldr = new GeneratedFontSkinLoader( new InternalFileHandleResolver(), this.fontsByName);\n\nthis.assetMgr.setLoader( Skin.class, ldr );\n\nthis.assetMgr.load( \"data\/uiskin.atlas\", TextureAtlas.class );\nthis.assetMgr.load( \"data\/uiskin.json\", Skin.class );\n\n\/\/ etc...\n}\n<\/pre>\n<p>The fonts can then be safely used in the JSON file. Just make sure the names line up with the keys used in initFonts.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\">com.badlogic.gdx.scenes.scene2d.ui.Label$LabelStyle: {\ndefault: { font: big-font, fontColor: red, font: big-font }\nhuge: { font: big-font, fontColor: red, font: huge-font }\nsmall: { font: big-font, fontColor: red, font: small-font }\n}\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>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&#8230;<\/p>\n","protected":false},"author":1,"featured_media":358,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[11],"tags":[],"class_list":["post-359","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-programming"],"_links":{"self":[{"href":"https:\/\/pmcgovern.ca\/wp\/index.php?rest_route=\/wp\/v2\/posts\/359","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/pmcgovern.ca\/wp\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/pmcgovern.ca\/wp\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/pmcgovern.ca\/wp\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/pmcgovern.ca\/wp\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=359"}],"version-history":[{"count":24,"href":"https:\/\/pmcgovern.ca\/wp\/index.php?rest_route=\/wp\/v2\/posts\/359\/revisions"}],"predecessor-version":[{"id":884,"href":"https:\/\/pmcgovern.ca\/wp\/index.php?rest_route=\/wp\/v2\/posts\/359\/revisions\/884"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/pmcgovern.ca\/wp\/index.php?rest_route=\/wp\/v2\/media\/358"}],"wp:attachment":[{"href":"https:\/\/pmcgovern.ca\/wp\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=359"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/pmcgovern.ca\/wp\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=359"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/pmcgovern.ca\/wp\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=359"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}