Search Unity

TextMesh Pro Callback when character not found?

Discussion in 'UGUI & TextMesh Pro' started by tessellation, Aug 6, 2020.

  1. tessellation

    tessellation

    Joined:
    Aug 11, 2015
    Posts:
    390
    When a glyph is not found in a font or any of it's fallbacks, I'd like to be able to write a global script handler (e.g. callback or C# event) so that I can report this error via analytics. I suspect there are some missing glyphs for certain users, but I'm not able to capture the full context unless I can log this error properly.

    Ideally the callback could be registered once for the TMPro system and not have to be set for individual TextMesh components.

    In the callback, I'd like to have access to the TextMesh being displayed and the missing character index and unicode value.
     
    viesc123 likes this.
  2. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    What do you mean by "TextMesh"? Do you mean the "Text" itself or the name of the text object?
     
  3. tessellation

    tessellation

    Joined:
    Aug 11, 2015
    Posts:
    390
    Sorry I meant TMP_Text - any component that derives from TMP_Text. The component displaying the text.
     
  4. tessellation

    tessellation

    Joined:
    Aug 11, 2015
    Posts:
    390
    I think I've identified a TextMeshProUGUI component that is responsible for at least one of these missing glyph errors. As a temporary workaround for this particular case, is there a way I can check from script to see if a TMP component will have missing glyphs before it's displayed?
     
  5. tessellation

    tessellation

    Joined:
    Aug 11, 2015
    Posts:
    390
    Never mind, I found it: TMP_FontAsset.HasCharacters(string)

    Although it appears this method doesn't search fallbacks, so I would have to iterate through the string character by character and use HasCharacter(char) instead.
     
  6. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    Code (csharp):
    1. public bool HasCharacters(string text, out uint[] missingCharacters, bool searchFallbacks = false, bool tryAddCharacter = false)
    This version of the HasCharacters function can search the fallbacks as well.
     
    viesc123 and tessellation like this.
  7. tessellation

    tessellation

    Joined:
    Aug 11, 2015
    Posts:
    390
    Great, thanks! I would still very much find the missing glyph callback I mentioned useful. There are cases where we display text that isn't completely predictable, for example "localizedPriceString" from UnityIAP that displays currencies with the price. Also user-entered text such as email and feedback. I also have some uppercase-only fonts and if the game accidentally displays mixed-case text without setting the uppercase style then it could report missing glyphs.
     
  8. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    A Callback for missing glyphs has been requested before so I will most certainly consider adding one.

    How about

    Code (csharp):
    1. public static event Action<int, string, TMP_FontAsset, TMP_Text> OnMissingGlyph;
    unicode, the text, the font asset and the text object.
     
  9. tessellation

    tessellation

    Joined:
    Aug 11, 2015
    Posts:
    390
    If it's easily available to you, the index of the missing glyph in the string might be helpful so we don't have to search for it. The string itself and the font asset would be available in the TMP_Text object, so you don't necessarily need to pass those values.

    When a missing glyph appears multiple times in a string, I assume we would get a callback for each occurrence? If so, then the index of the missing glyph would be quite useful.
     
  10. tessellation

    tessellation

    Joined:
    Aug 11, 2015
    Posts:
    390
    So after playing around with this some more, I can see that for error reporting purposes, it might be a better design to just have one callback per TMP_Text object that reports if there were any missing characters. So for example, the callback could be formatting like this:

    Code (csharp):
    1. public static event Action<TMP_Text, List<uint> missing> OnMissingCharacters;
    The missing character error messages that TMP logs could also be logged once per object (instead of per-character) and look something like this:

    Characters were not found in [OstrichSans-Medium] font asset or any fallbacks: \u0E14 \u0E35 \u0923. Replaced by Unicode character \u0020 in text object [Text].
     
  11. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    Note: Did not see your last message before posting this.

    Tracking of missing characters (plural) would be more complex given a text object can use multiple font assets each potentially missing some characters.

    In addition, instead of firing the event as each character is parsed, this process would have to be delayed to issue the event once the list has been compiled.

    Using the below revised event would enable you to easily track each missing character in different font assets and even text objects. Your code would own the data structure used to track these missing characters.

    Thoughts?

    I changed the signature as follows to include the "stringIndex" of the missing character.

    Code (csharp):
    1.  
    2. /// <summary>
    3. /// Delegate used by the OnMissingGlyph event called when the requested Unicode character is missing from the font asset.
    4. /// </summary>
    5. /// <param name="unicode">The Unicode of the missing character.</param>
    6. /// <param name="stringIndex">The index of the missing character in the source string.</param>
    7. /// <param name="text">The source text that contains the missing character.</param>
    8. /// <param name="fontAsset">The font asset that is missing the requested characters.</param>
    9. /// <param name="textComponent">The text component where the requested character is missing.</param>
    10. public delegate void MissingGlyphEventCallback(int unicode, int stringIndex, string text, TMP_FontAsset fontAsset, TMP_Text textComponent);
    11.  
    12. /// <summary>
    13. /// Event delegate to be called when the requested Unicode character is missing from the font asset.
    14. /// </summary>
    15. public static event MissingGlyphEventCallback OnMissingGlyph;
    16.  
    I kept the font asset because, the text might be referencing a font asset which could be different then the font asset assigned to the text object.
     
    Last edited: Aug 8, 2020
  12. tessellation

    tessellation

    Joined:
    Aug 11, 2015
    Posts:
    390
    Ah, yes, of course because of rich tag fonts and styles. That's also why a single error per TMP object wouldn't necessarily apply to a single font asset unless the list of missing characters was a list of structs containing the uncode value, the string index, and the font asset.
     
  13. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    Re-post of my Edit above.

    Tracking of missing characters (plural) would be more complex given a text object can use multiple font assets each potentially missing some characters.

    In addition, instead of firing the event as each character is parsed, this process would have to be delayed to issue the event once the list has been compiled.

    Management of the data structure to track this would have to be owned by TMP which is not ideal.

    On the other hand, using the below (now in previous post) revised event would enable you to easily track each missing character in different font assets and even text objects. Your code would own the data structure used to track these missing characters.

    For instance, in your event handling code, you could add each font asset to some data structure along with the missing characters for this font asset. You could track this per text object as well, etc. Basically, you would be free to structure the tracking anyway you see fit.

    Thoughts?
     
  14. tessellation

    tessellation

    Joined:
    Aug 11, 2015
    Posts:
    390
    Thanks. Yes, I see what you're saying because often TMP reports the missing glyphs for the same TMP object multiple times (due to our localization system causing multiple text rebuilds). We'd probably want to collect missing glyphs for particular objects in a Dictionary and report them only once after performing some kind of de-duplication.
     
  15. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    Correct.

    TMP would simply fire an event whenever a missing character is encountered and passing along the relevant data for you to track it in whatever data structure you need.
     
  16. tessellation

    tessellation

    Joined:
    Aug 11, 2015
    Posts:
    390
    Oh and... when you refactored TextMeshPro to have a separate character and glyph table, I expect now we're technically talking about missing "characters" here, not glyphs. Just something to think about when naming the callback and delegate type. ;)
     
  17. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    Yes. Already realized this while replying to previous posts so I already renamed this potential new event to "OnMissingCharacter".
     
    tessellation likes this.
  18. Thaina

    Thaina

    Joined:
    Jul 13, 2012
    Posts:
    1,165
    Do we already have this feature or it was just planned?

    Also I wish that, we would have a boolean settings to let text mesh pro search for native system fonts and then load any font in the system that contains the missing character first before firing this event, is it possible?

    Alternatively, we might have this callback return list of glyph, and so we might able so load some specific font for known glyph. Then return a list of glyphs that still missing to have text mesh pro continued searching in the OS (and we might just return null to not search in the OS)
    With this method, TMPro could also keep a list of glyph that cannot be able to found in the device, and don't need to search for it ever again
     
    Last edited: Dec 18, 2020
  19. tessellation

    tessellation

    Joined:
    Aug 11, 2015
    Posts:
    390
    That sounds like a significant expense to load every system font to search for glyphs, especially on desktops with hundreds of fonts. I think if you want to do this then there are ways in Unity for you to do this yourself and then create your own dynamic font that TMPro can fall back to. On mobile I personally would not want the cost of loading time and memory usage that would be required to parse these system fonts, some being very large files like Noto CJK on Android.
     
  20. Thaina

    Thaina

    Joined:
    Jul 13, 2012
    Posts:
    1,165
    I don't think we would load every fonts in one go, instead we would iterate the system font, one by one, until we found a font that contains a missing glyph. We then have TMP create font asset from that font file and add it as a fallback

    And then we don't need to do that again if there was no character from any new code block
     
  21. tessellation

    tessellation

    Joined:
    Aug 11, 2015
    Posts:
    390
    From my experience, it never looks good to just randomly pick a font to use to stand-in for a missing glyph. Every font has different metrics, proportions, weights and styles. At least for our commercial game product, this isn't something we'd be likely to use, but I don't see any barriers to writing this script yourself - my opinion is that it should not be part of the core TMPro library.
     
  22. Thaina

    Thaina

    Joined:
    Jul 13, 2012
    Posts:
    1,165
    Looking good is not so important than conveying message first and foremost if we play online game and want to let player use chatbox or custom name there would always be a chance that player from unknown place will use their own language in it. If we know the font are not looking good we can update our manual font fallback list later. But that require font finding process and app update and so usability should come first