Skip to content

tommyettinger/fontwriter

Repository files navigation

fontwriter

Generates and optimizes Structured JSON fonts for TextraTypist.

Also stores pre-made Structured JSON fonts here in the repo.

tl;dr

This is a command-line tool that takes .ttf or .otf files and makes bitmap fonts out of them for libGDX to use.

You can load Structured JSON fonts this makes with TextraTypist or BitmapFontBridge. TextraTypist uses them natively and supports the better-when-scaled "distance field fonts" with its own Font class, while BitmapFontBridge makes normal libGDX BitmapFont objects, and doesn't do anything special with distance fields.

OK, More Detail Please...

TextraTypist has the class Font, a... fairly complete replacement of the BitmapFont code in libGDX. It supports various extra features, including rendering signed distance field (SDF) and multichannel signed distance field (MSDF) fonts out-of-the-box. These are ways of rendering fonts using a different shader internally, and allow one font image to scale smoothly to much larger sizes without losing much quality. However, generating these fonts can be a challenge; MSDF fonts in particular only had a few options (such as msdf-gdx-gen, an inspiration for this project), and most of the existing options had to clumsily wrap the one utility for generating MSDF fonts... one character at a time. Having to stitch together sometimes hundreds of individual textures, one per char, tended to cause issues where letters would "wobble" up and down over a sentence, especially for smaller chars or fonts with many chars. The author of MSDF and most utilities surrounding it relatively-recently wrote msdf-atlas-gen, which can output to several font file formats, but not the AngelCode BMFont format that libGDX and TextraTypist can natively read. It can produce JSON, though!

The Structured JSON msdf-atlas-gen produces can be read in by TextraTypist to produce working fonts. Support for loading Structured JSON is present in TextraTypist from 1.0.0 onward. That release also includes BitmapFontSupport, which can load a libGDX BitmapFont from a Structured JSON font file, and FWSkin to load fonts produced by "FW" (FontWriter, this project) as BitmapFont or Font. Another micro-library exists, FreeTypist, to load FreeType font configuration in a way that both Font and BitmapFont can read, as well as loading those from .fnt or .json files. There's also now BitmapFontBridge, which avoids any dependency on TextraTypist, and produces only libGDX BitmapFonts from Structured JSON (or AngelCode BMFont) files.

Do I need to run this at all?

Possibly not! There are quite a few pre-made Structured JSON fonts in a variety of styles in the docs/knownFonts folder. You can copy the .json.lzma font file, the .png file with the same filename but different extension, and any license file(s) related to that font. Paste them into your assets folder in a libGDX project, and load the .json.lzma file using code from TextraTypist or BitmapFontBridge. Using BitmapFontBridge, you can load Structured JSON into BitmapFont objects. Otherwise, you can use TextraTypist's Font constructor that takes as its last parameter boolean ignoredStructuredJsonFlag (its value doesn't matter, just that you pass a boolean there). You can also use FWSkin, FreeTypistSkin, FWSkinLoader, or FreeTypistSkinLoader if you use these with scene2d.ui. From then on, the font acts like a normal Font or BitmapFont. You probably shouldn't try loading distance field fonts with BitmapFont; it may be possible to load DistanceFieldFont objects later. For now, "standard" is the best option with BitmapFont, and "standard" or "sdf" are good options for Font.

Note that the .json files are generated by msdf-atlas-gen, and there are a few other types of compressed (binary) versions of the corresponding .json files, such as .json.lzma, .ubj, .ubj.lzma, and .dat . You only need one such file per font, plus the matching .png with the same name (minus extensions). The .json files are larger, but human-readable and human-editable, somewhat. It isn't often that you'll need to edit one of the .json files, but .json.lzma files will allow you to at least read the file if you extract the .json out of it with a tool like 7-Zip, and are close to the smallest files here. You can also use LzmaUtils.decompress() in TextraTypist to extract a .lzma file with code, or use the Lzma class in libGDX.

The actual smallest files are the .ubj.lzma files, which aren't always compatible with GWT (due to a bug in libGDX), and can't be made human-readable easily. The file size will matter less in a JAR, but it matters a lot for a Git repo like this one or like TextraTypist that hosts a lot of fonts. The bug affecting UBJ files is present in libGDX 1.13.1, but not the subsequent 1.13.5 or 1.14.0 releases. Avoiding UBJ is still a good idea unless you know you will never target the browser; the size difference is small. TeaVM might not have the same bug in some versions or even in any version; it isn't yet clear.

This can generate SDF and MSDF fonts about as easily as it can "standard" (non-distance field) fonts, and so there are SDF and MSDF versions of every "standard" font here. Crispness is handled automatically by information in the .json or .dat file, and by TextraTypist. This is an improvement over the .fnt approach, which couldn't store crispness info in the file. BitmapFont doesn't store any info about distance fields in its files, or even in its class. There's a DistanceFieldFont subclass of BitmapFont in libGDX, but it hasn't been used much.

OK, how do I use this?

This has always worked on Windows, but now we have a working release for macOS (x64 and arm64) and Linux (x64)!

If you have the JAR from the releases, unzip it so the other files it came with are all in the same folder structure. Then, you can enter the directory with that holds fontwriter-2.2.13.1.jar and run java -jar fontwriter-2.2.13.1.jar "MyFont.ttf" standard 60 , where "MyFont.ttf" can be any path to a .ttf file or an .otf file (.ttc may work). "MyFont.ttf" doesn't have to be in the same folder if you give it an absolute path (on Windows, you can drag and drop a file after typing java -jar fontwriter-2.2.13.1.jar to enter its absolute path). The second parameter, standard, can also be sdf or msdf. You might just want standard for many reasons; even though it won't scale up nicely, it will scale down fairly well, and you can interchange standard fonts using FontFamily or using colorful emoji. On the other hand is msdf, which scales up very well, but looks a little odd with colorful emoji. Then sdf is somewhere in the middle; it works somewhat well with emoji, though it doesn't handle their partially transparent edge very well, scales up nicely, and optionally can allow a shader to automatically outline text. The "60" parameter is a size, I think measured in pt or px. It isn't necessarily going to be used as-is; if the size is too large, progressively smaller sizes will get tried until all glyphs fit. You can optionally specify a size of image to write (the default is 2048x2048, and fonts that only use ASCII probably don't need that much space) as the next parameter. After that you can optionally specify a color by name (such as "black" or "red") or RGB hex code (such as "BB3311"; RGBA also works but alpha is ignored), which will write an extra preview of all chars using that color. The fifth argument is there so that you can quickly see all chars, even on a white background. The extra previews won't look very good if you're using msdf or sdf, but the regular preview will typically be more clear/crisp. An optional sixth argument is a path (local or absolute) to a folder containing I18N files: either text files that have names starting with "lang_" or files with the extension ".properties". If any files are present with matching names in that folder, their contents will be used to determine the character set used by the font. If the sixth argument is not present or is a non-existent path, then the folder containing the font file is used, and will look for the same name patterns in the folder with the font file. If no text files have matching names, then every char in the font will be used in the generated files. If you have lang_* or *.properties files in your project, it is suggested to put them in their own folder or folders and specify the path yourself, rather than relying on having a mix of I18N and font files in one folder.

Running that command will try the size you give it first, and if it can't fit all chars in the font into a 2048x2048 (or other size, if specified) image, it will reduce the size and try again, repeatedly. Once it can fit everything, it saves the file into fonts/, including saving various compressed variants on the .json file, then starts doing some TextraTypist-related processing.

It paints a small "block" of solid white pixels into the lower right corner, then (if using sdf or standard) optimizes the image so that it only uses the alpha channel with white pixels. That last step helps texture filtering; without it, fully transparent pixels are fully-transparent black rather than fully-transparent white, and mixing with black will darken sometimes even if the mixed color is transparent (with white usually doesn't do this). It then optimizes the image in fonts/ with oxipng, and generates a preview image that it places in previews/. If you specified a color name or hex code, it also writes a preview of all chars in that color. It then optimizes the preview image(s), and then you're done!

As an overview: The minimal arguments are the font's path, the distance field type, and the max font size. The maximal arguments are the font's path, the distance field type, the font size, the size of the image to write (such as 4096x4096, with an x separating width and height), and a color (by name or hex code). All arguments are positional; the order is the only thing that matters. Arguments after the font size are optional, and default to 2048x2048 and "" (without the color argument, it won't make an extra preview).

Windows Binaries? Gross!

Well, it turns out this isn't the case anymore, and we can now include "gross binaries" from oxipng's official releases as well as Linux and Mac ones built by GitHub Actions from (my fork of) the msdf-atlas-gen repo. Big thanks to @EvergineTeam for setting up GH Actions in a PR to the main msdf-atlas-gen repo! On Windows, the PR's changes don't seem to produce a working binary, so we use the official .exe (version 1.4) there. I don't actually know if the binaries on Linux or Mac work. I don't have a Mac to test on and haven't yet tested on Linux, so if they work on the first try... I'll be surprised. Windows x64 works, at least.

In earlier versions, msdf-atlas-gen binary used this from in fontwriter 1.0.4 to 2.0.0: version v1.3, and older versions of fontwriter depended on a self-built, slightly modified fork of msdf-atlas-gen. That self-built modification isn't needed anymore; EvergineTeam's changes have been applied cleanly to version 1.3 of the official msdf-atlas-gen repo.

The oxipng binary used this in earlier releases: version v9.0.0 (which is a little old by now), or this: version v9.1.1 starting in fontwriter 1.0.3, or this: version v10.1.0 starting in fontwriter 2.2.13.

License

This uses the Apache License v2.0.

The included msdf-atlas-gen uses the MIT License. The version used here is built by GitHub Actions rather than being built by Chlumsky, but is still MIT-licensed.

The included oxipng also uses the MIT License.