Search Unity

TextMesh Pro Generate Font atlas and Save Automatically

Discussion in 'UGUI & TextMesh Pro' started by better_walk_away, May 20, 2019.

  1. better_walk_away

    better_walk_away

    Joined:
    Jul 12, 2016
    Posts:
    291
    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.
     
    Last edited: May 20, 2019
  2. Errorsatz

    Errorsatz

    Joined:
    Aug 8, 2012
    Posts:
    555
    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.
     
    Last edited: Feb 25, 2020
    better_walk_away likes this.
  3. better_walk_away

    better_walk_away

    Joined:
    Jul 12, 2016
    Posts:
    291
    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...
     
  4. Errorsatz

    Errorsatz

    Joined:
    Aug 8, 2012
    Posts:
    555
    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.
     
    better_walk_away likes this.
  5. better_walk_away

    better_walk_away

    Joined:
    Jul 12, 2016
    Posts:
    291
    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.
     
    Last edited: Mar 3, 2020
  6. tessellation

    tessellation

    Joined:
    Aug 11, 2015
    Posts:
    390
    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:

    Code (CSharp):
    1. font.atlasPopulationMode = AtlasPopulationMode.Dynamic;
    2. font.isMultiAtlasTexturesEnabled = true;
    3. font.ClearFontAssetData(true);
    4. font.TryAddCharacters(glyphsUsed, true);
    5. font.atlasPopulationMode = AtlasPopulationMode.Static;
    6. TMPro_EventManager.ON_FONT_PROPERTY_CHANGED(true, font);
    7. EditorUtility.SetDirty(font);
     
  7. andrew_pearce_

    andrew_pearce_

    Joined:
    Nov 5, 2018
    Posts:
    169
    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:

    Code (CSharp):
    1. var script = (FontManagerScriptableObject)target;
    2. if (GUILayout.Button("Update Atlas Texture", GUILayout.Height(32))) {
    3.   TMPro_FontAssetCreatorWindow.ShowFontAtlasCreatorWindow(script.GetFontAsset());
    4. }
    2) You can easily edit generation settings (in my case I am updating custom character lists based on the text files used in app:

    Code (CSharp):
    1. FontAssetCreationSettings facs = fontAsset.creationSettings;
    2. facs.characterSetSelectionMode = 7;
    3. facs.characterSequence = "character list here";
    4. 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
     
    better_walk_away likes this.
  8. hessel_mm

    hessel_mm

    Joined:
    Mar 12, 2014
    Posts:
    44
    I think this has changed since your post, it seems that the font asset window overwrites the creationSettings' character sequence and mode:

    Code (CSharp):
    1.  
    2. window.LoadFontCreationSettings(fontAsset.creationSettings);
    3.  
    4. // Override settings to inject character list from font asset
    5. window.m_CharacterSetSelectionMode = 6;
    6. window.m_CharacterSequence = TMP_EditorUtility.GetUnicodeCharacterSequence(TMP_FontAsset.GetCharactersArray(fontAsset));
    7.  
    (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.
     
    andrew_pearce_ likes this.
  9. andrew_pearce_

    andrew_pearce_

    Joined:
    Nov 5, 2018
    Posts:
    169
    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.
     
  10. hessel_mm

    hessel_mm

    Joined:
    Mar 12, 2014
    Posts:
    44
    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.
     
  11. andrew_pearce_

    andrew_pearce_

    Joined:
    Nov 5, 2018
    Posts:
    169
    @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.

    Code (CSharp):
    1.     public bool TryAddCharacters(uint[] unicodes);
    2.     public bool TryAddCharacters(uint[] unicodes, out uint[] missingUnicodes);
    3.     public bool TryAddCharacters(string characters);
    4.     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.
     
    Last edited: May 21, 2021
  12. hessel_mm

    hessel_mm

    Joined:
    Mar 12, 2014
    Posts:
    44
    Ah yes, I forgot, I had to update the TMP package too.
    TryAddCharacters
    was added only recently.
     
  13. andrew_pearce_

    andrew_pearce_

    Joined:
    Nov 5, 2018
    Posts:
    169
    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

    Code (CSharp):
    1. //window.m_CharacterSetSelectionMode = 6;
    2. //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)

    Code (CSharp):
    1. using UnityEditor;
    2. using System.IO;
    3.  
    4. [InitializeOnLoad]
    5. public class TextMeshProFixer {
    6.   private static string tmpFilePath = "Library/PackageCache/com.unity.textmeshpro@1.5.6/Scripts/Editor/TMPro_FontAssetCreatorWindow.cs";
    7.   private static string issueLine1 = "window.m_CharacterSetSelectionMode = 6;";
    8.   private static string issueLine2 = "window.m_CharacterSequence = TMP_EditorUtility.GetUnicodeCharacterSequence(TMP_FontAsset.GetCharactersArray(fontAsset));";
    9.  
    10.   static TextMeshProFixer() {
    11.     if (File.Exists(tmpFilePath)) {
    12.       string fileContent = File.ReadAllText(tmpFilePath);
    13.       fileContent = fileContent.Replace(issueLine1, "");
    14.       fileContent = fileContent.Replace(issueLine2, "");
    15.  
    16.       StreamWriter writer = File.CreateText(tmpFilePath);
    17.       writer.Write(fileContent);
    18.       writer.Flush();
    19.       writer.Close();
    20.     }
    21.   }
    22. }
     
    Last edited: Jul 17, 2021
  14. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    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.
     
  15. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,609
    The current workflow to update font atlases after localization changes in my game is:
    1. Click button to pull unique characters from localization and save as "characters_<language>.txt" file. These files are assigned to TMP font assets.
    2. Select TMP font asset
    3. Click "Update Font Atlas" in Inspector
    4. Change "Character Set" to "Characters from file" (this option is lost always see here)
    5. 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.
     
    Last edited: Sep 26, 2021
  16. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,609
    @Stephan_B Any news on adding a public "GenerateFontAtlas" method as I describe right above this post?
     
    IcyHammer likes this.
  17. awallick-sd

    awallick-sd

    Joined:
    May 29, 2019
    Posts:
    13
    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!
     
  18. CameronND

    CameronND

    Joined:
    Oct 2, 2018
    Posts:
    88
    Yes please add way to generate atlas from code.
     
  19. imurashka

    imurashka

    Joined:
    Aug 5, 2012
    Posts:
    20
    Much needed feature
     
  20. IcyHammer

    IcyHammer

    Joined:
    Dec 2, 2013
    Posts:
    71
    @Stephan_B Any news about the suggested feature of adding the method GenerateFontAtlas?
     
  21. Dust999Games

    Dust999Games

    Joined:
    Jan 10, 2014
    Posts:
    7
    Hello @Stephan_B

    I'm the same here. I'm using I2localize on the project. And have about 13 languages, cyrillic, asian with complicated glyphs. I'm trying to automatize TMP creating/updating assets flow. I'm creating a charset for each font but can't automatize creating/updating TMP font assets.