Search Unity

Issue using the TMPro fallback system to render Chinese or Japanese w/ specific fonts

Discussion in 'UGUI & TextMesh Pro' started by mikest, Apr 19, 2021.

  1. mikest

    mikest

    Joined:
    Feb 23, 2015
    Posts:
    29
    Hi, I'm wanting to render Chinese and Japanese each with a specific font in my game and have a fairly large character set for each one. I have a base font used for English and Latin characters and then have created separate font assets for each of the Asian languages and added those as fallback options in the base font asset.

    So to clarify:
    for English and latin characters, I use Font A
    for Japanese, I use Font B, paste in my characters and set it as the first fallback in Font A
    for Chinese, I use Font C, paste in my characters and set it as the second fallback in Font A

    The issue seems to be that Japanese and Chinese use the same unicode values for characters so that whatever font is second in the list (in this case, Chinese) will render most text TMPro displays in a mix of the Font B and C.

    a few notes:
    I don't know if these are the same characters across languages, but this seems strange to me.
    Font B is a stylized manga font and has 500 missing characters if i try to use it in Chinese so that's not an option.

    Is there any way to handle this situation using the fallback system only and have them render the right font?
    Or will I have to switch to a "common" font (like Noto or Arial) that supports all characters in both languages?
    I also don't want to have to write some custom component to do font swapping as this could be a nightmare as it's a huge RPG with lots of text and UI.
     
  2. mikest

    mikest

    Joined:
    Feb 23, 2015
    Posts:
    29
  3. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    Sorry about the delayed response. I read your post on Monday but never got around to replying.

    Short answer: That is indeed the solution.

    Longer answer:
    As you have discovered many of the fonts designed for CJK include font face variants for each local. These are commonly packaged in a TrueType Collection font (.ttc) like Noto Sans CJK as seen below.

    upload_2021-4-22_15-14-43.png

    Although many characters and glyphs in these font faces share the same visual design, that is not the case for all of them.

    From Unicode FAQ - Chinese and Japanese
    "There are occasional instances of unified characters whose typical Chinese glyph and typical Japanese glyph are distinct enough that the Chinese glyph will be unfamiliar to the typical Japanese reader, e.g., 直 U+76F4. To prevent legibility problems for Japanese readers, it is advisable to use a Japanese-style font when presenting Unihan text to Japanese readers."

    Since in most cases, it is not possible to determine which specific glyph should be used based on the text itself, we have to look elsewhere like system local, user language selection in the application, etc. to select the appropriate font face / glyph and in our case potential fallback.

    Localization can be pretty complex as in many cases it involves a lot more than just the content of the text but potentially changing font asset, fallbacks, text settings, etc. but it can also involve many other assets like sprites, images, color palettes, sounds, etc.

    In my opinion this is where a localization system whose function is to manage these resources and what happens when a user selects a different language and how this language selection affects it all. There are several localization tools available. Unity is also working on such a tool as well so I encourage you to look in this if you haven't already done so.

    Having said that, I do not think any of them currently support to re-ordering of fallback font assets based on local selection but that is most certainly a feature they should be adding. I have thought about adding some type of support for re-ordering fallbacks based on language selection but I really think this is something that a localization system should handle and not something a font asset should be burdened / aware of (if that makes sense).

    So in the meantime, my suggestion is exactly as you posted, re-order the fallbacks based on language selection. I believe the current font asset API exposes all the functionality you are likely to need there. If not, please let me know.
     
  4. mikest

    mikest

    Joined:
    Feb 23, 2015
    Posts:
    29
    Thanks for the response and the additional details. I am using the I2 Localization asset but it's way of handling font changes did not seem like a good solution to me and i believe it would require some rework that seems unnecessary when reordering the fallbacks seems like a much cleaner solution.

    I agree with you that this seems like a localization tool feature and not a TextMeshPro feature.

    thanks again.
     
  5. mikest

    mikest

    Joined:
    Feb 23, 2015
    Posts:
    29
    I got a chance to take a look at this today and ran into a problem. Let me explain what I am doing and what happened and maybe you have some ideas.

    First, I have 3 fonts in use in my game for various places and UIs

    So i created a singleton I called the "LanguageManager" with a holder property for each font asset i am using in the game.

    Code (CSharp):
    1. [SerializeField] private TMP_FontAsset[] fontAssets;
    Then on startup and when the language is changed I am iterating through the font assets, assigning the current fallback table (i tried m_FallbackFontAssetTable and fallbackFontAssetTable) to a list, trying select the new "top fallback" font with LINQ and then adding that to an "update" list and removing it from the current list. Then I add the rest of the current fonts to the "update" list.

    so this kind of works.... but it also doesn't.

    Basically, the fallback table for all three fonts gets set to the LAST font processed.
    Also, this persists to the editor and the font asset is not marked dirty so it's a little tricky to revert since i have to make a change to it.

    My guess is that I am making a bad assumption somewhere.

    Code below:

    Code (CSharp):
    1.     void updateFallbackOrder() {
    2.  
    3.         if (!Language.IsAsianCharacter)
    4.             return;
    5.  
    6.         List<TMP_FontAsset> update = new List<TMP_FontAsset>();
    7.         List<TMP_FontAsset> current = new List<TMP_FontAsset>();
    8.  
    9.         foreach (var asset in fontAssets) {
    10.  
    11.             update.Clear();
    12.             current.Clear();
    13.             current.AddRange(asset.m_FallbackFontAssetTable);
    14.  
    15.             // based on language we should resort the fallbacks
    16.             if (Language.IsJapanese) {
    17.  
    18.                 var jpFont = current.FirstOrDefault(f => f.name.Contains("Japanese") || f.name.Contains("JP"));
    19.                 current.Remove(jpFont);
    20.                 update.Add(jpFont);
    21.  
    22.             } else if (Language.IsChinese) {
    23.  
    24.                 var cnFont = current.FirstOrDefault(f => f.name.Contains("Chinese") || f.name.Contains("SC"));
    25.                 current.Remove(cnFont);
    26.                 update.Add(cnFont);
    27.  
    28.             }
    29.  
    30.             update.AddRange(current);
    31.  
    32.             asset.m_FallbackFontAssetTable = update;
    33.  
    34.         }
    35.  
    36.     }