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.
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?
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.
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.
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.
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:
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
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.
/// <summary>
/// Delegate used by the OnMissingGlyph event called when the requested Unicode character is missing from the font asset.
/// </summary>
/// <param name="unicode">The Unicode of the missing character.</param>
/// <param name="stringIndex">The index of the missing character in the source string.</param>
/// <param name="text">The source text that contains the missing character.</param>
/// <param name="fontAsset">The font asset that is missing the requested characters.</param>
/// <param name="textComponent">The text component where the requested character is missing.</param>
public delegate void MissingGlyphEventCallback(int unicode, int stringIndex, string text, TMP_FontAsset fontAsset, TMP_Text textComponent);
/// <summary>
/// Event delegate to be called when the requested Unicode character is missing from the font asset.
/// </summary>
public static event MissingGlyphEventCallback OnMissingGlyph;
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.
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.
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.
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.
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.
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.
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
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.
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