Search Unity

TMP 1.4.1 Creating Static Font Assets via Editor Script

Discussion in 'UGUI & TextMesh Pro' started by Ferazel, Jul 31, 2019.

  1. Ferazel

    Ferazel

    Joined:
    Apr 18, 2010
    Posts:
    517
    This was originally posted in the 1.4.1 but was requested that I move to a standalone topic.

    I'm trying to port my localization system from 1.3.x to 1.4.1, but I'm running into an issue where the font generation has been refactored and am hoping that there is an easy upgrade path for my use case. One of the responsibilities of the localization system is to read all of the characters used in the localization and create Font Assets for the languages that need a unique font (KO, JA, HANS, etc.) or have a very specific character set (RU). Then using those characters generate static font assets that can be loaded at runtime as a fallback to the default English Font assets (assigned to all of the prefabs).

    Since I already know the characters that we need in the game and the Asian font file is adding 16.5 MB of app size, we want to bake out the Font Assets as static fonts so that they don't store a reference to the otf. Is there an easy API to bake out static font assets? Is my best bet to try to duplicate the TMPro_FontAssetCreatorWindow script where it generates a font asset?

    I understand my use case is pretty rare, but I figured I'd ask if I was missing anything that might help me out with the new system. Thanks!
     
    Last edited: Jul 31, 2019
  2. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    In the latest release of TextMesh Pro with the new Dynamic SDF support which are versions 1.4.x for Unity 2018.4 and 2.0.x for Unity 2019.x, you can now create font assets at runtime (which you don't need in your case) and using the following function.

    Code (csharp):
    1. public static TMP_FontAsset CreateFontAsset(Font font, int samplingPointSize, int atlasPadding, GlyphRenderMode renderMode, int atlasWidth, int atlasHeight, AtlasPopulationMode atlasPopulationMode = AtlasPopulationMode.Dynamic)
    What is currently missing from the above function is the auto size of the point size but since you are creating those as fallbacks, you need to make sure you keep the same ratio of Sampling Point Size to Padding so most likely you are not using auto-size.

    You would then use the following additional function:

    Code (csharp):
    1. public bool TryAddCharacters(uint[] unicodes, out uint[] missingUnicodes)
    To add the characters to the font asset. There is another overload of this function that takes in a string.

    Currently missing from the above function is the ability to extract the font features like Glyph Pair Adjustments (Kerning) which I will be adding.

    You will have to create a persistent asset out of this newly created font asset and add the material and atlas texture to it as a sub object just like it is done in the Font Asset Creator code. Since everyone now has access to the source code, it is easy to see how that is handled.

    You will need to switch that font asset to static otherwise the font file will get dragged with it in builds.

    ....

    An alternative option... since the new system is dynamic, you could actually create a font asset similar to how it is done with the TMP_FontAsset_CreationMenu.cs file. This would create an empty font asset but a persistent asset. This only works in the Editor but that is what you want anyway.

    Then, you could actually create a text object using the newly created font asset and then set the .text property using a string that contains all the characters you wish to add. This would add all the glyphs and even fetch the kerning data. Then you would simply again change the font asset to static.

    In the next release which will include Multi Atlas support, this same technique would actually add all the characters into the first atlas texture and then create additional one automatically to add the remaining glyphs / characters.

    I haven't tested the above for the purpose you describe but it should work. What is also nice, is the ability to change the creation settings which would affect how many atlas texture end up being created. So even if you initially selected an atlas width and height that was resulted in too many atlas textures, changing the creation setting to something larger would replace / reduce the number of textures and add all the glyphs back in automatically.

    I suggest you play / experiment with the above potential workflows and let me know what works best for you or if I overlooked anything.
     
  3. Ferazel

    Ferazel

    Joined:
    Apr 18, 2010
    Posts:
    517
    Yeah, after spending some time with this. I don't think there's a perfect solution for us.

    We actually were letting the fonts autosize to different point sizes. Which I understand will create visual discordance between the primary font glyphs and fallback fonts. Primarily we took this approach, because synchronizing a standard size between all languages is kind of difficult and we didn't want our primary languages to suffer because Chinese needed a smaller point size.

    Creating the font asset through the dynamic public API works, but it requires a rigid size which we'd prefer not to do. The CreationWindow process accesses a lot of internal types, methods, and APIs that are not accessible by default. I could bring the code into the Unity assemblies but it obviously impacts upkeep on the package which I don't like.

    So what I want is a weird Frankenstein model.
    I'd love to have the TryPackGlyphsInAtlas method public so I can go through the auto-size algorithm (or if you want to make yours available as a static method that'd be super).
    Then I'd love to be able to specify the kerning table via a public API (or have the font be able to do the lookup on it via the add characters API).

    My current plan is to try to go through the process and use reflection (sigh) to try to get around the internal keywords, but I realize that's extremely fragile as well.

    Edit: I ended up doing something very similar to what you recommended which is to create a TMP_Mesh and set the .text to get the kerning pairs and write a custom resize algorithm when the TryAddCharacters fails (I have a separate check to make sure the font supports it, so if the TryAddCharacters fails it is due to the size which I can iterate at a different size).

    I am curious on how much less optimized the SDF texture is packed when building the atlas with a dynamic layout, but the speed increase for SDFAA is pretty dang amazing when building it for the editor.
     
    Last edited: Aug 3, 2019
  4. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    Adding an additional parameter to support "Auto-size" when creating font assets via CreateFontAsset() function is on my list. Not sure it makes a lot of sense for Runtime but not sure it makes sense either to restrict that flavor of the function to the editor only. I guess making that parameter optional and false by default might be the way to go.

    The same is true for another parameter to make it possible to retrieve the Font Features such as Kerning / Glyph Pair Adjustment Records, etc. is also on the list.

    One of the things you mentioned was trying to avoid having to include the font file which dynamic mode requires in order to keep the size of your app down.

    One option here is the ability to create font assets at Runtime from local fonts on devices which should now be possible in the latest 2018.4 using a combination of the new GetPathsToOSFonts() which returns an array that contains the path of all the fonts on the device. Then using the Font constructor which I modified internally where if you provide it a path, it will create a Font that includes the font data which can then be used with the CreateFontAsset() function.

    The challenge with the above is being dependent on what is available on the various devices where you will most likely be forces to pick different fonts. Then you also have less peace of mind in terms of Quality Assurance vs static where you know exactly what glyphs you include and at what quality and how everything will look before you ship. And have to run full dynamic. Etc...

    On the other hand, you could create Asset Bundles that contain the font assets and other resources for various languages or groups of languages. This would enable you to ship with Primary font assets that contain all the known characters used in the project and then rely on dynamic fallbacks for the other characters that will come from user input / other sources.

    In addition to the above, the Multi Atlas features (which will be in the next release) will allow font assets to contain more than (1) atlas texture where additional textures are created as needed (when the current atlas is full). This functionality applies to all font assets so your primary could remain static and the fallbacks dynamic with multi atlas texture enabled.

    Or have your primary be dynamic with Multi Atlas enabled where the first atlas or more would already contain all the known characters and then at runtime have the unknown characters being added to a partially filled texture or a new one created as needed. I would need to benchmark this last option but I believe the performance with respect to the glyphs / characters already included would be close to static so that might be a nice option.

    Let me know what you think...
     
  5. Ferazel

    Ferazel

    Joined:
    Apr 18, 2010
    Posts:
    517
    Thanks for the reply and help getting this working.

    It would be nice to have an editor-only static bake method on the TMP_FontAsset to do what the font creator does and static bake of the font (auto-size the font size, optimal glyph packing, recording kerning pairs). For games like ours that has 95% of the text coming directly from the localization source assets it makes sense to keep our fonts static.

    My understanding is that if I switch the dynamically created font asset to static in the editor script that bakes the font after the font glyphs have been recorded by the TMP_Text object, the font will remove the font source reference even if the source was originally a dynamic font. Is that correct?

    In regards to asset management of the localization. It is definitely something that we've talked about, but one of our tenets is to try to make our apps playable after the install without additional downloads. Requiring language pack downloads would not be acceptable for something as fundamental as localization. So we need to pack the font assets into the base app install right now.

    I appreciate being able to potentially grab the OS font and setting up a dynamic runtime font without embedding large font asset files. If there is a need, I'll probably setup a ternary font fallback (English/Euro, Localized Font, SystemFont) in case we need to display text from external sources. However, I don't think I'll implement that now because even that won't cover all of our dynamic needs.

    Our users expect native app functionality and that includes having access to the full OS emoji library. They can have nicknames that are created using the native keyboard and many users embed system emoji library into their names. I'm guessing we're quite a bit a ways away from having the entire system emoji library available in a dynamic font. Until that happens though, we will likely only use TMP for our in-game static strings and in the rare cases we need dynamic text support on a custom (but slow) solution of marshalling bitmap data from native to Unity.
     
    Last edited: Aug 4, 2019
  6. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    That is correct. Once switched to Static, the Editor only reference to the source font file will remain but the Runtime one will be set to null thus excluding the source font file from the build.

    Now that extracting font data from native font file to create font assets is available using the process I outlined in my previous reply, I will be adding the ability to rasterize colored glyph so that we can access those native Emojis. No firm ETA yet but that feature is on the short term list which I am hoping I'll be able to make accessible back to 2019.1.
     
    Ferazel likes this.
  7. skykevin9

    skykevin9

    Joined:
    Jun 13, 2019
    Posts:
    7
    Hi @Stephan_B

    We have encountered a similar issue as described in this thread. Our app supports several different languages and all the translations are in an Excel file. Each time the file is updated, the translator will execute a script to export all the characters to a Character Set txt file. After that we updated the SDF font file by opening the TMP Font Asset Creator and click Generate then Save.

    The translations are updated frequently and we want to make the whole process automated by script and integrated into our in-house CI system. We have checked the Generate and Save process as implemented in TMPro_FontAssetCreatorWindow.cs file, but we find that the FontEngine.TryPackGlyphsInAtlas and FontEngine.RenderGlyphsToTexture methods are protected or private. We cannot call them and we want to use auto-size point size.

    Hope you could understand the situation and is there a workaround for this?

    Unity version 2019.4.28, TMP version 2.1.6

    Thank you. :)
     
    Last edited: Nov 24, 2021
  8. skykevin9

    skykevin9

    Joined:
    Jun 13, 2019
    Posts:
    7

    OK. Finally I find the TMP_FontAsset.TryAddCharacters and it works! Remember to change the font asset to dynamic!