Generate Font atlas and Save Automatically

Hello,
I am using TextMeshPro in my project. My game supports Chinese, I type the Chinese characters mostly in the scripts, not in the input field of a TextMeshProUGUI component, so I can’t use the Dynamic SDF System, I have to generate a “character file” that lists all the Chinese characters that appear in the game so that I can generate the font asset through TextMeshPro’s Font Asset Creator.
I had written a custom editor that found every Chinese character that appears in my scripts and in the input fields of my custom components, and outputted the characters string as a txt file, I called this custom editor my “Chinese Characters Finder”. Then I open the Font Asset Creator, and then build the Font Asset.
However, I found it time-consuming that every time I want to update my Chinese font asset, I have to run my Chinese Characters Finder, and then navigate to Window→TextMeshPro→Font Asset Creator→Generate Font atlas→Save. How can I combine these steps into one so that every time I run my Chinese Characters Finder, it will generate the font asset and save it for me?
Chinese is not like English, Chinese contains thousands of characters, so it’s very likely that we have to update our Chinese font asset when we type in some new Chinese sentences because they may contain new Chinese characters that are not in the font asset. We have to update the font asset quite a lot of times, if we can generate font atlas and save it automatically, we can save a lot of working hours that are wasted on navigating through TextMeshPro’s Font Asset Creator.

I’d be interested in an answer to this one as well. TMPro_FontAssetCreatorWindow does all the necessary steps, but many of the methods it uses are internal and not available from an outside script.

The use case is that we have some languages where due to size reasons, we want to only include a subset of the characters in the atlas. The “characters from file” option works fine for this, but manually using the UI to update fonts whenever the localization changes is tedious and opens the possibility of errors. This would be perfect for an editor script … if it’s possible to access the necessary API.

1 Like

Yes, I am still using the old way to update my font asset, going through Window→TextMeshPro→Font Asset Creator→Generate Font atlas→Save. There is still no official API for this…

For anyone searching for this in future, I was able to find a solution, but it’s not a nice one.

  1. TMPro_FontAssetCreatorWindow has all the code necessary to generate and save font atlases. You’ll probably want to strip it down for your use case, and it needs some adjustment to work via script instead of GUI, but that’s fairly straightforward.
  2. That code uses a number of methods and fields which are set to internal visibility.
  3. Using reflection, you can bypass that internal visibility.

This is obviously not an ideal approach, since those methods are not part of the public API and are subject to change whenever TMP is updated. However, it does work.

1 Like

Another thing that is quite inconvenient is that the configuration of the Font Asset Creator would be lost when switching between projects. For example, I set the setting of the Font Asset Creator in project A, maybe I want to create a Font Asset for font A with size 80. Then, I close project A and open project B, I set the setting of the Font Asset Creator in project B, this time, maybe I want to create a Font Asset for font B with size 60. Finally, I switch back to project A, we should see that the setting of the Font Asset Creator in project A has been lost because we change the setting in project B.
Also, I think the setting of the Font Asset Creator should depend on individual font, otherwise, we have to reset the setting each time we want to build Font Asset for different font. Right now, the design of Font Asset Creator kind of assumes that we only use one Font Asset and rarely need to build the Font Asset, which is not the case in Chinese.
If TextMeshPro could provide some API, it is definitely a timesaver.

I recommend you use v1.5.0.preview5 because you can update multi or single atlas SDFAA fonts using scripts with the new TryAddCharacters() method. Something like this:

font.atlasPopulationMode = AtlasPopulationMode.Dynamic;
font.isMultiAtlasTexturesEnabled = true;
font.ClearFontAssetData(true);
font.TryAddCharacters(glyphsUsed, true);
font.atlasPopulationMode = AtlasPopulationMode.Static;
TMPro_EventManager.ON_FONT_PROPERTY_CHANGED(true, font);
EditorUtility.SetDirty(font);
4 Likes

Just in case if someone is looking for the same. Here is my solution I found:

  1. Create CustomEditor for your class with following button. It will open Font Creator popup with font asset you currently use in your class (GetFontAsset is a public method but you can make font asset publicly visible), it should be TMP_FontAsset type:
var script = (FontManagerScriptableObject)target;
if (GUILayout.Button("Update Atlas Texture", GUILayout.Height(32))) {
  TMPro_FontAssetCreatorWindow.ShowFontAtlasCreatorWindow(script.GetFontAsset());
}
  1. You can easily edit generation settings (in my case I am updating custom character lists based on the text files used in app:
FontAssetCreationSettings facs = fontAsset.creationSettings;
facs.characterSetSelectionMode = 7;
facs.characterSequence = "character list here";
fontAsset.creationSettings = facs;

I plan to add also “Check Font Asset” button which will check font asset and makes sure that all letters exist and if they do not exist then it should add a generate button which will update character sequence and opens font creator with updated settings, so I will need just click on Generate Font Atlas and save changes. Of course you can do whole generation by your own, the code is located here \Library\PackageCache\com.unity.textmeshpro@1.3.0\Scripts\Editor\TMPro_FontAssetCreatorWindow.cs line 609

I hope this info saves someones time

1 Like

I think this has changed since your post, it seems that the font asset window overwrites the creationSettings’ character sequence and mode:

window.LoadFontCreationSettings(fontAsset.creationSettings);

// Override settings to inject character list from font asset
window.m_CharacterSetSelectionMode = 6;
window.m_CharacterSequence = TMP_EditorUtility.GetUnicodeCharacterSequence(TMP_FontAsset.GetCharactersArray(fontAsset));

(From TMPro_FontAssetCreatorWindow)

Which is a shame, really. This is something I’d like to be able to automate as much as possible.

And sure, the dynamic mode is nice, but the 10+ mb that full unicode fonts can easily add to your app feels like a waste if all your text is static.

1 Like

You are absolutely right. Even more, I was promised by @Stephan_B to be replied on 21st January. I created a test project to demonstrate the issue. It’s 9th of May and I am still waiting for any update on this case =) I believe I “grow up” to move to Unreal sooner than I will get a solution. As a workaround, I copy the characters which needs to be added to buffer and simply paste them manually (Ctrl + V) but I still need to change mode manually.

I eventually got it to work with @tessellation 's method; Changing the font to dynamic, clear it, set characters and then set it back to static. It feels extremely hacky, but it works.

I can sympathize with the deflating feeling when running into issues like this though. There are so many areas of Unity that feel unfinished, missing fairly obvious features. It also feels that on some features (e.g. text rendering) are comically understaffed (at least that is what it appears like). Instead of solidifying the base Unity seems to be focussed on half-launching somewhat-promising features. And then killing them again.

But Unreal, or any other package, sounding better is probably very much a “grass being greener on the other side of the fence” type of situation.

@hessel_mm the problem is not about where the grass is greener but the TMP I am using has four version of TryAddCharacters and none of them works as @tessellation suggested =) Seems like I need to update TMP.

    public bool TryAddCharacters(uint[] unicodes);
    public bool TryAddCharacters(uint[] unicodes, out uint[] missingUnicodes);
    public bool TryAddCharacters(string characters);
    public bool TryAddCharacters(string characters, out string missingCharacters);

I can successfully cleanup the font asset but I am not able to add anything…

I follow the principle of YAGNI - “You aren’t gonna need it”. Unless it’s critical, I will NEVER update any plugin or Unity itself. When you are working on a project, you do not want to waste a week trying to fix what an update broken (unless you have thousands of unit tests), instead of working on a new feature.

Ah yes, I forgot, I had to update the TMP package too. TryAddCharacters was added only recently.

Finally I find time to give it another try. I should confirm that for TMP 1.5.3 the function TryAddCharacters() does not work. So I simply downgrade it back to 1.3.0 and my hack is still working. I have no idea why I did not downgrade before.

UPD1: Whenever I post a message on a forum, I am able to find a working solution. All you need to do is to comment 66 and 67 lines in the file (from Project panel) Packages/TextMeshPro/Scripts/Editor/TMPro_FontAssetCreatorWindow.cs in TMP 1.5.3

//window.m_CharacterSetSelectionMode = 6;
//window.m_CharacterSequence = TMP_EditorUtility.GetUnicodeCharacterSequence(TMP_FontAsset.GetCharactersArray(fontAsset));

Those are the lines which @hessel_mm mentioned in his post. After that, my hack above works flawelessly =)

UPD2: Seems like Unity is trying to reset the changes. Here is the patch (create the file TextMeshProFixer.cs and place it in any Editor folder, if your project do not have one, just create anywhere in Asset folder)

using UnityEditor;
using System.IO;

[InitializeOnLoad]
public class TextMeshProFixer {
  private static string tmpFilePath = "Library/PackageCache/com.unity.textmeshpro@1.5.6/Scripts/Editor/TMPro_FontAssetCreatorWindow.cs";
  private static string issueLine1 = "window.m_CharacterSetSelectionMode = 6;";
  private static string issueLine2 = "window.m_CharacterSequence = TMP_EditorUtility.GetUnicodeCharacterSequence(TMP_FontAsset.GetCharactersArray(fontAsset));";

  static TextMeshProFixer() {
    if (File.Exists(tmpFilePath)) {
      string fileContent = File.ReadAllText(tmpFilePath);
      fileContent = fileContent.Replace(issueLine1, "");
      fileContent = fileContent.Replace(issueLine2, "");

      StreamWriter writer = File.CreateText(tmpFilePath);
      writer.Write(fileContent);
      writer.Flush();
      writer.Close();
    }
  }
}

Can you provide more information about how is TryAddCharacters() not working?

I would like to fix whatever needs fixing there so you can use the latest version of the package.

Since packages are supposed to be immutable, any local changes will get overwritten whenever you close / re-open Unity. However, if you make the changes in the Global Package Cache then those changes will be persistent.

The current workflow to update font atlases after localization changes in my game is:

  • Click button to pull unique characters from localization and save as “characters_.txt” file. These files are assigned to TMP font assets.

  • Select TMP font asset

  • Click “Update Font Atlas” in Inspector

  • Change “Character Set” to “Characters from file” (this option is lost always see here )

  • Click “Generate Font Atlas”

Then repeat step 2-5 for several fonts and hoping not to miss one.

To make things more streamlined and less error-prone, can you please add a public GenerateFontAtlas(font, charactersfile) method or similar functionality?

I would like to integrate font atlas generation in step 1, so I can update everything localization related with a single button click.

3 Likes

@Stephan_B Any news on adding a public “GenerateFontAtlas” method as I describe right above this post?

1 Like

Hello! I’m trying to do something very similar to @Peter77 . Just wondering if there’s any updates along those lines, or to bump if not. Would love to programmatically generate font atlases with updated character text files as part of my localization tooling! Thanks!

Yes please add way to generate atlas from code.

Much needed feature

@Stephan_B Any news about the suggested feature of adding the method GenerateFontAtlas?