Dynamic fonts aren't practical for mobile apps with CJK

I love the new realtime font atlas generation stuff, it has the huge advantage that you don’t need to generate atlases for languages that you aren’t using. But it’s not practical for mobile apps. Why? Because typically the source TTF fonts are massive and take up too much space (for example, Noto Sans for CJK in two weight, 6 fonts, is over 100 MB which is more than our entire APK size). Also you’re typically only using a small subset of glyphs on the screen at any one time. So I have a couple new feature ideas on this:

Optimized TTF source assets: In my game, I have a tool that parses all localized text and it can generate a character “usage list” for a particular language. I then use these character strings to generate the static fonts. However, even these atlases are large, about 1MB each (1024x1024) for each asian language and each font style. I’d like to generate these dynamically, but like I said above, the TTF source fonts are prohibitively large for mobile. What would be great is if TMP could generate an optimize source TTF asset that only has a subset of glyphs in it for use in font generation. This would make dynamic font generation possible on mobile.

Glyph Atlas Caching: The second idea involves glyph atlas caching. Using “most-recently-used” (MRU) rules, glyphs would be generated into a grid-based atlas that has limited slots. So you could choose a 1024x1024 SDF with, say, slots for 256 glyphs (16x16 grid). The atlas could even be used by multiple fonts. Then for whatever glyphs are being displayed at the moment, these are generated if they aren’t already in the atlas cache, pushing out older glyphs. If the atlas cache is too small, one glyph might be generated twice in the same frame. This would raise a “churn” warning to the developer, which indicates that the atlas cache needs to be larger or an additional atlas needs to be added to the cache. This could even be done automatically, with the developer able to set a limit on the number of atlases allowed in the cache, depending on the working memory of your game. Being able to “lock” glyphs in the cache would be useful (for example numbers). One thing that would need to be looked into is how much texture upload hit that caching would cause if many atlases were being modified in a single frame. In my use-case, the text changes infrequently, for example when a UI pops up or a screen changes. So at first look this seems like a good solution.

@Stephan_B Hoping you’ve had a chance to think about this. :slight_smile:

An alternative would be support for loading font data directly from the OS. That would keep the APK slim and wouldn’t miss any character, at the cost of having less control over which font is used.

I had a chance to review it when you initially posted it but didn’t have a chance to reply yet.

As per @Neto_Kokku reply, the solution is to create dynamic font assets at runtime from font files available on the target platforms.

This can be accomplished by using the following

// Get a list of all font files and their paths available on the current platform
string[] fontPaths = Font.GetPathsToOSFonts();

// Create a new font object using one of those OS font file paths.
Font osFont = new Font(fontPaths[index]);

// Create new dynamic font asset.
TMP_FontAsset fontAsset = TMP_FontAsset.CreateFontAsset(osFont);

Assuming you already know what font files are available on the target platform and their paths, you can then skip using the GetPathsToOSFonts() function.

I will be making an additional improvement to allow skipping having to create a Font object where you will be able to use the font file path in the CreateFontAsset().

The above CreateFontAsset() functions uses the following default values and calls the function below.

CreateFontAsset(font, 90, 9, GlyphRenderMode.SDFAA, 1024, 1024, AtlasPopulationMode.Dynamic);
public static TMP_FontAsset CreateFontAsset(Font font, int samplingPointSize, int atlasPadding, GlyphRenderMode renderMode, int atlasWidth, int atlasHeight, AtlasPopulationMode atlasPopulationMode = AtlasPopulationMode.Dynamic, bool enableMultiAtlasSupport = true)
1 Like

Thanks guys. No offense, but your answer(s) are clearly written from the perspective of a programmer and not a designer. Operating system fonts are as generic as possible. Not to be snarky, but Unity is a game engine. Games have personality and character (as do many apps). Well designed, professional games have fonts that match the character of the game and UI. Often several different fonts and styles/weights are required to achieve a good design. Droid Serif or Roboto or whatever Helvetica-type font that comes with Android is not an acceptable font for many games.

Secondly, this doesn’t address the second suggestion I made about Glyph Atlas Caching. You could still end up with many atlases for a given language, where the majority of the contents are taken up by unused glyphs. The only way to keep these down would be to occasionally throw out all atlases and rebuild them when you exceed a certain atlas count limit.

Thanks for thinking about this from another perspective.

None taken as I am a programmer :slight_smile:

Despite this potential short coming :wink: I am a programmer that understands that I am developing a tool for our users and that such tool must satisfy their needs. Needs that I can better understand through communications such as these on the forum.

We are in total agreement here. I wish more users understood and appreciated the above as too often text and font selection is an after thought.

Technical challenges aside, I think modifying the source font file would be an issue on the licensing front with many fonts.

I certainly agree that including some of these larger font files in your APK is an issue. What about downloadable content via AssetBundles or Addressables. Could some of these payloads by downloaded to the device as needed?

Currently, we have four (4) sources for characters and glyphs.

  • Static font asset that are self contained (ie. no font file needed).
  • Dynamic Font Asset that include the source font file in the APK.
  • Downloadable content via AssetBundles and Addressables. These could include static font assets or font files, etc.
  • OS / Platform fonts from which we can create dynamic font assets at runtime.

Is there another category or option that I am perhaps overlooking?

The above would be a trade off between inclusion of static atlas texture(s) (affecting build size) and memory overhead (loaded atlas texture(s) footprint) vs performance overhead (glyph rasterization / management at runtime).

I think having a Glyph Atlas Caching system where all glyphs share the same set of textures would be nice. The performance overhead would not be that different from the dynamic system. Again, I think it would be best to pre-populate these on scene loading to hide the performance overhead.

I would likely shy away from trying to deal with glyph life cycle / MRU as I think the additional performance overhead vs benefit would not warrant it. Especially if we are using different fonts where shapes and size vary greatly. This would results in wasting time trying to fit a glyph in many potential empty spaces that are too small or too big, etc…

I would lean towards the same handling as Multi Atlas Texture does which is to simply add stuff as needed to textures and create new ones as needed. It is not as efficient on the packing side but it also doesn’t waste time trying to pack.

I would also keep the atlas sizes static and keep those in an array so that we can enable Texture Arrays in the Shader (in the future) which would allow these to only use 1 draw call regardless of the number of texture in the array.

Users should be able monitor usage and to Clear() the cache as needed which should be done again on scene loading / less time / performance sensitive times.

The system would also need to allow users to add characters and glyphs to font assets but without rasterizing the glyphs. This would make it possible to adjust metrics and font features (adjustment pairs, diacritical marks, ligatures, etc.)

When you have time, send me a PM and perhaps we can discuss this over Zoom or something.