Search Unity

TextMesh Pro Full Emoji Support Api (emoji Sequen

Discussion in 'Unity UI (uGUI) & TextMesh Pro' started by Rafael_CS, Apr 12, 2019.

  1. Rafael_CS

    Rafael_CS

    Joined:
    Sep 16, 2013
    Posts:
    69
    Hello guys, i want to share this improvement API to TextMeshPro

    Download Link: EmojiSequenceSearchApi_1.1.unitypackage

    This package will include all you need to support all Emojis of Android/iOS in you app (Unicode Emoji 12.0)

    The original TextMeshPro use unicode characters to try map Emojis, but this is problematic because in Android we have a huge amount of Emojis that use Char Sequence.

    Thats why you need my simple API:

    FAST GUIDE:
    • Replace your TextMeshProUGUI component to TMP_EmojiTextUGUI component in the GameObject

      upload_2019-4-12_1-29-4.png
    • Enable RichText (we need it because we use <sprite=index> tag to map the emojis).

    • Add in TMP SETTINGS (or in your TMP_EmojiTextUGUI) the spriteasset in correct format
      (generated by Sprite Importer)

      upload_2019-4-12_1-53-24.png
    PS: Already added the EmojiData_google in this project so you can use it.
    (License Apache License 2.0)

    This SpriteAsset contains all emojis from Android 9.0 in 32x32 per emoji format.

    USING CONVERSION TOOL TO GENERATE SPRITEASSETS:
    • Download a JSON and a Spritetexture from https://github.com/iamcal/emoji-data
    • Access in Unity the tool 'EmojiData to TexturePacker Json'
      (Path: "Window/TextMeshPro/Convert EmojiData to TexturePacker JSON")
    • Drop the JSON downloaded from emoji-date in the tool and configure the size/spacing/padding of the grid in SpriteSheet (default size 32x32, with spacing 2x2 and padding 1x1)
    • Hit the convert button.

      upload_2019-4-12_1-51-48.png
    • Find the generated file (with name texturepacker_<OriginalFileName>) in same folder of the json used as parameter.
    • Use generated file in Sprite Importer Tool ("Window/TextMeshPro/Sprite Importer") with the spritesheet downloaded in emoji-data
    • Save your SpriteAsset and use it in TextMeshPro (yey!)

      upload_2019-4-12_1-46-36.png
    ENJOY the Full Unicode Emoji 12.0 Support! YEY!
    • Here an example of a chat system implemented with this Api
      upload_2019-4-12_1-26-24.png

    ABOUT SEARCH ENGINE IMPLEMENTATION:
    • The emoji sequence will be extracted from TMP_Sprite.name in UTF32 or UTF16 HEX format separeted by '-' for each char (see example below)
      Ex: TMP_Sprite.name = 0023-fe0f-20e3.png

      0023-fe0f-20e3.png will generate:
      Unicode 00000023 and
      Sequence 00000023 0000fe0f 000020e3

      so this a valid CharSequence and must be included in SearchEngine because TMP_Text dont know how to handle with it
      (the sequence != unicode representation)

      But

      0023.png will generate:
      Unicode 00000023 and
      Sequence 00000023

      so this will be ignored by the searchengine (default TMP_Text can handle this case without extra overheads because the sequence == unicode representation)

      PS1: The pattern is the default names in emoji-date JSONs and in EmojiOne JSONs
      PS2: The CharSequence will be ignored if UnicodeHex == Name.ToHex() or if the Name is not in correct pattern.

    • Search engine will try cache all Emoji Sequences of a SpriteAsset in two lookup tables

    • The first table will Map the CharSequence to SpriteIndex
      (example: sequence U+1f3c4 U+200d U+2640 U+fe0f will be mapped to <Sprite=512>)

    • The second Dictionary will map all paths until the end.
      (Example: in Sequence U+1f3c4 U+200d U+2640 U+fe0f we will generate entry for all chars util the end because we need O(1) access while trying to find if a char is mapped as a sequence (or if i can give up and just leave this char in final text)

      FastPath Entries for U+1f3c4 U+200d U+2640 U+fe0f
      U+1f3c4
      U+1f3c4 U+200d
      U+1f3c4 U+200d U+2640
      U+1f3c4 U+200d U+2640 U+fe0f


    • During parse process it will check char by char, looking at FastPathDictionary. if we failed to find entry in FastPath we try to retrieve sprite from CharSequenceToSpriteIndex table (with current path checked until last iteration). If failed we leave this char alone (this is not an emoji sequence).
    • The process is very eficient because we acess all dictionaries in O(1), so the order of complexity to parse a text is O(N) while N = text.length
      (with minor extra overheads during the search).
    • The SearchEngine will only parse text when something changed in TMP_EmojiTextUGUI
    • The original text in TMP_EmojiTextUGUI will not be affected because the parse process don't save the parsed text in m_text (only in the charbuffer)

    If the creator of TextMeshPro need any help to integrate this to original TMP_Text,
    just send me a message or e-mail.
    (raf.csoares@gmail.com or raf.csoares@kyubinteractive.com)
     
    Last edited: Oct 13, 2019
    nat-soragge likes this.
  2. George-Dolbier

    George-Dolbier

    Joined:
    Sep 1, 2013
    Posts:
    9
    Most excellent, I just updated to 2019.1 and have a project of moderate size that relies heavily on TMP, (It's a sudoku derivative) and now I need emojis. So I just downloaded it, and going to give it a try. I'll email you if I have any deep technical issues.
     
    Rafael_CS likes this.
  3. David-Alaniya

    David-Alaniya

    Joined:
    Nov 9, 2014
    Posts:
    1
    Hello! Emojis are small(
    Is it possible to scale them?
     
  4. Rafael_CS

    Rafael_CS

    Joined:
    Sep 16, 2013
    Posts:
    69
    Just download the version in 64x64 in https://github.com/iamcal/emoji-data and follow the tutorial in my post.
    But take care, Highend Mobile Devices can only run textures with 4096x4096(in low end devices this number is limited to 2048x2048).
     
  5. daniel_unity938

    daniel_unity938

    Joined:
    Jun 18, 2018
    Posts:
    5
    Generated emojis from sheet_apple_32 of emoji data doesn't work with the generated JSON, I've noticed that their table uses 0.41em as the padding.

    Is there anything i need to change to support the latest emoji data?
     
  6. igor_rst

    igor_rst

    Joined:
    Apr 20, 2017
    Posts:
    27
    Thanks for your asset! Maybe you can do it for TMP InputField too?)

    Or I can change TMP text component in InputField to this?

    upd: yeah, it works

    upd2: no :( input field shows emoji, but when try to edit error occurs
     
    Last edited: Sep 12, 2019
  7. wateeeeeeeer

    wateeeeeeeer

    Joined:
    Sep 13, 2019
    Posts:
    2
    Hey Rafael_CS just wanted to say thank you very much for this! I was wrangling my head around character sequences support! Thanks again
     
    Rafael_CS likes this.
  8. Rafael_CS

    Rafael_CS

    Joined:
    Sep 16, 2013
    Posts:
    69
    Can you give me more details about it?
     
    Last edited: Oct 10, 2019
  9. Rafael_CS

    Rafael_CS

    Joined:
    Sep 16, 2013
    Posts:
    69
    Change the padding while generating the JSON, you can do it in the Editor Window Tool of my asset
     
  10. igor_rst

    igor_rst

    Joined:
    Apr 20, 2017
    Posts:
    27
    I don't remember now, need reproduce it again.
    But I know that fallback atlases support works incorrect, cuz for each fallback atlas tablekey will be rewrited.

    I have 2 atlases, atlas 2 in fallback list for atlas 1
    Input emoji from atlas 1, it will paste smth like that:
    Code (JavaScript):
    1. from atlas 1 <sprite=10> text
    And now if I input emoji from atlas 2, with index 10 it will paste the same
    Code (JavaScript):
    1. from atlas 1 <sprite=10> text, from atlas 2 <sprite=10> test
    Will show 1 emoji from atlas 1, but should be different. Emoji from atlas 2 must have other index.
     
  11. igor_rst

    igor_rst

    Joined:
    Apr 20, 2017
    Posts:
    27
    I think, need some edits with that part:

    Code (CSharp):
    1.                                
    2. string tableKey = tableBuilder.ToString ();
    3. if (!string.IsNullOrEmpty (tableKey) && !lookupTableSequences.ContainsKey (tableKey)) {                            
    4.   lookupTableSequences[tableKey] = j;
    5. }
    6.  
    Cuz in loop for each atlas, j variable will be increment from zero
     
  12. igor_rst

    igor_rst

    Joined:
    Apr 20, 2017
    Posts:
    27
    So, I fixed that issue by adding atlas name support.

    Code (CSharp):
    1. from atlas 1 <sprite="atlas1" index=10> text, from atlas 2 <sprite="atlas2" index=10> test
     
    Rafael_CS likes this.
  13. Rafael_CS

    Rafael_CS

    Joined:
    Sep 16, 2013
    Posts:
    69
    Nice, i will update with this modification support

    [ChangeLog Version 1.1]

    Changed parser to support fallbacks.
    Code (CSharp):
    1. <sprite name="char sequence">
    Doing this we can prevent index problems and avoid necessity to put spriteatlas inside the correct folder.

    Thanks igor for the bug report
     
    Last edited: Oct 14, 2019
    igor_rst likes this.
  14. igor_rst

    igor_rst

    Joined:
    Apr 20, 2017
    Posts:
    27
    So, can you test this realization with TMP Input field? Maybe I do smth wrong..
     
  15. Rafael_CS

    Rafael_CS

    Joined:
    Sep 16, 2013
    Posts:
    69
    I will try take a look.

    But the inputfield in mobile, just send text in input to the tmp_text ... so, if the inputfield send the sequence of characters to tmp_text, the algorithm should decode and replace to a emoji
     
  16. odysoftware

    odysoftware

    Joined:
    Jul 21, 2015
    Posts:
    52
    I used your generated json file and everything is working fine except the specific icon which consinst of multiple unicode characters (like the flags!)

    When using your TMP_EmojiTextUGUI I get the same wrong behaviour as when using the normal TextMeshProUGUI component?

    The flags are shown as multiple icons as can be seen here (this is now with EmojiTextUGUI): Screenshot 2019-10-17 at 11.43.05.png

    It should only show the austria flags (that was what I entered) - so the second flags before are all wrong.

    Shouldn't the TMP_EmojiTextUGUI parse this correctly - I was thinking this is the reason for this control or am I wrong?

    Thanks in advance for your time and efforts,
    Oliver
     
  17. odysoftware

    odysoftware

    Joined:
    Jul 21, 2015
    Posts:
    52
    I can see that your searchengine is correcty changing m_text to the correct sprite name (which is als available in table!) then calling ParseInputText() and then reverting. But in game it still looking with the multi icons?

    I am using Unity 2019.2.8f1
     
  18. odysoftware

    odysoftware

    Joined:
    Jul 21, 2015
    Posts:
    52
    When removing the line:


    Code (CSharp):
    1. //We must revert the original text because we dont want to permanently change the text
    2.                 m_text = v_oldText;
    then it works fine? I guess it does not work when temporary changing m_text and calling ParseInputText() in 2019.2.8f1?
     
  19. igor_rst

    igor_rst

    Joined:
    Apr 20, 2017
    Posts:
    27
    I checked it again and found out that emoji pasts correct, but caret works strange after emoji paste/remove. After some text and emoji, caret position works incorrect.
     
  20. odysoftware

    odysoftware

    Joined:
    Jul 21, 2015
    Posts:
    52
    Also when using Multi-Icons, even though displaying works, I can see several warning message in unity from TMP telling that some ASCII cannot be found in fallback value?

    Code (CSharp):
    1. Character with ASCII value of 9792 was not found in the Font Asset Glyph Table. It was replaced by a space.
    2. UnityEngine.Debug:LogWarning(Object, Object)
    3. TMPro.TextMeshProUGUI:SetArraySizes(UnicodeChar[]) (at Library/PackageCache/com.unity.textmeshpro@2.0.1/Scripts/Runtime/TMPro_UGUI_Private.cs:1316)
    4. TMPro.TMP_Text:ParseInputText() (at Library/PackageCache/com.unity.textmeshpro@2.0.1/Scripts/Runtime/TMP_Text.cs:1716)
    5. TMPro.TMP_Text:GetPreferredWidth() (at Library/PackageCache/com.unity.textmeshpro@2.0.1/Scripts/Runtime/TMP_Text.cs:3536)
    6. TMPro.TextMeshProUGUI:CalculateLayoutInputHorizontal() (at Library/PackageCache/com.unity.textmeshpro@2.0.1/Scripts/Runtime/TextMeshProUGUI.cs:93)
    7. UnityEngine.Canvas:ForceUpdateCanvases()
     
  21. igor_rst

    igor_rst

    Joined:
    Apr 20, 2017
    Posts:
    27
    It is not icons problem. It means that there is no symbol in your font.
     
  22. odysoftware

    odysoftware

    Joined:
    Jul 21, 2015
    Posts:
    52
    Yes sure, but it only happens when using the multi-icons, I know that we don't have the symbols for multi icons, because the multi icons use multiple single ASCII values for one icon. So I guess this is why we get this error, but can't this be avoided?
     
  23. IvanTroho

    IvanTroho

    Joined:
    Aug 2, 2018
    Posts:
    2
    Hello, Rafael! I got some problem loading 64x64 emojis. I downloaded spritesheet and json from https://github.com/iamcal/emoji-data and using emoji.json in "EmojiData to TexturePacker" to get texturepacker. In "Sprite importer" i use this texturepacker and sheet_google_64.png. Then i use generated asset in TMP settings but it does not showing emojis in text output. I think this happens because i`am using wrong values of "grid size", "padding", "spacing" but i have no idea where i can get proper values. I will be very appreciated for your help
     
  24. Rafael_CS

    Rafael_CS

    Joined:
    Sep 16, 2013
    Posts:
    69
    @IvanTroho here the setup for image sprite_sheet_64.png
    upload_2019-11-14_21-33-47.png

    As downloaded image of google_sheet_64 is not power of 2 image, i converted it to 4096x4096 with dock in Top-Left (using paint.net) before create the sprite asset in Unity.
    upload_2019-11-14_21-48-36.png
    upload_2019-11-14_21-49-9.png

    Dont forget to edit texture properties in Unity:
    * Uncheck "generate Mipmaps"
    * Check "alpha is transparent"
    * Set "Max Size" to 4096

    upload_2019-11-14_21-42-6.png

    [OPTIONAL] if you are using the google_sheet_64 with non-power of two size (the one downloaded from web with size 3498x3498) set "non power of two" to "None" and Compression to "None". I really recommend to edit image in photoshop to keep size in power of two(4096x4096), so you can use compression instead of consume 46mb of memory (as you can see in image above, the compressed memory size was 16mb instead of 46mb)

    Best Regards
    Rafael
     
    IvanTroho likes this.
  25. Rafael_CS

    Rafael_CS

    Joined:
    Sep 16, 2013
    Posts:
    69
    Probably something changed in new unity/textmeshpro of new unity.

    I must investigate the problem
     
  26. IvanTroho

    IvanTroho

    Joined:
    Aug 2, 2018
    Posts:
    2
    Thank you so much, it works like a charm!
     
    Rafael_CS likes this.
  27. Rafael_CS

    Rafael_CS

    Joined:
    Sep 16, 2013
    Posts:
    69
    @odysoftware
    Replace the TMP_EmojiTextUGUI script code to this new one and the emoji will work again in unity 2019.2

    Dependences:
    * TextMeshPro 1.3 in Unity <= 2018.2
    * TextMeshPro 2.0 in Unity >= 2018.3

    TextMeshPro 2.1 Preview not supported yet (the creator change some class names and it will give compile errors in Editor Inspector)


    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5.  
    6. namespace TMPro
    7. {
    8.     public class TMP_EmojiTextUGUI : TextMeshProUGUI
    9.     {
    10.         #region Private Fields
    11.  
    12.         protected bool m_emojiParsingRequired = true;
    13.  
    14.         #endregion
    15.  
    16.         #region Properties
    17.  
    18. #if UNITY_2018_3_OR_NEWER
    19.         protected System.Reflection.FieldInfo _isInputParsingRequired_Field = null;
    20. #endif
    21.         protected internal bool IsInputParsingRequired_Internal
    22.         {
    23.             get
    24.             {
    25. #if UNITY_2018_3_OR_NEWER
    26.                 if (_isInputParsingRequired_Field == null)
    27.                     _isInputParsingRequired_Field = typeof(TMP_Text).GetField("m_isInputParsingRequired", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
    28.  
    29.                 if (_isInputParsingRequired_Field != null)
    30.                     return (bool)_isInputParsingRequired_Field.GetValue(this);
    31.                 else
    32.                     return false;
    33. #else
    34.                 return m_isInputParsingRequired;
    35. #endif
    36.             }
    37.             protected set
    38.             {
    39. #if UNITY_2018_3_OR_NEWER
    40.                 if (_isInputParsingRequired_Field == null)
    41.                     _isInputParsingRequired_Field = typeof(TMP_Text).GetField("m_isInputParsingRequired", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
    42.  
    43.                 if (_isInputParsingRequired_Field != null)
    44.                     _isInputParsingRequired_Field.SetValue(this, value);
    45. #else
    46.                 m_isInputParsingRequired = value;
    47. #endif
    48.             }
    49.         }
    50.  
    51. #if UNITY_2018_3_OR_NEWER
    52.         protected enum TextInputSources { Text = 0, SetText = 1, SetCharArray = 2, String = 3 };
    53.         protected System.Reflection.FieldInfo _inputSource_Field = null;
    54.         protected System.Type _textInputSources_Type = null;
    55. #endif
    56.         protected TextInputSources InputSource_Internal
    57.         {
    58.             get
    59.             {
    60. #if UNITY_2018_3_OR_NEWER
    61.                 if (_inputSource_Field == null)
    62.                     _inputSource_Field = typeof(TMP_Text).GetField("m_inputSource", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
    63.  
    64.                 if (_inputSource_Field != null)
    65.                     return (TextInputSources)System.Enum.ToObject(typeof(TextInputSources), (int)_inputSource_Field.GetValue(this));
    66.                 else
    67.                     return TextInputSources.Text;
    68. #else
    69.                 return m_inputSource;
    70. #endif
    71.             }
    72.             set
    73.             {
    74. #if UNITY_2018_3_OR_NEWER
    75.  
    76.                 if (_inputSource_Field == null)
    77.                     _inputSource_Field = typeof(TMP_Text).GetField("m_inputSource", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
    78.  
    79.                 if (_inputSource_Field != null)
    80.                 {
    81.                     //Pick the Type of internal enum to set back in TMP_Text
    82.                     if (_textInputSources_Type == null)
    83.                         _textInputSources_Type = typeof(TMP_Text).GetNestedType("TextInputSources", System.Reflection.BindingFlags.NonPublic);
    84.                     if (_textInputSources_Type != null)
    85.                     {
    86.                         _inputSource_Field.SetValue(this, System.Enum.ToObject(_textInputSources_Type, (int)value));
    87.                     }
    88.                 }
    89.  
    90. #else
    91.                 m_inputSource = value;
    92. #endif
    93.             }
    94.         }
    95.  
    96.         #endregion
    97.  
    98.         #region Emoji Parser Functions
    99.  
    100.         protected virtual bool ParseInputTextAndEmojiCharSequence()
    101.         {
    102.             m_emojiParsingRequired = false;
    103.  
    104.             //Only parse when richtext active (we need the <sprite=index> tag)
    105.             if (m_isRichText)
    106.             {
    107.                 var v_parsedEmoji = false;
    108.                 var v_oldText = m_text;
    109.  
    110.                 v_parsedEmoji = TMP_EmojiSearchEngine.ParseEmojiCharSequence(spriteAsset, ref m_text);
    111.  
    112.                 m_emojiParsingRequired = false;
    113.                 IsInputParsingRequired_Internal = false;
    114.                 InputSource_Internal = TextInputSources.Text;
    115.  
    116.                 ParseInputText();
    117.  
    118.                 m_emojiParsingRequired = false;
    119.                 IsInputParsingRequired_Internal = false;
    120.  
    121.                 //Debug.Log("ParseInputTextAndEmojiCharSequence");
    122.                 //We must revert the original text because we dont want to permanently change the text
    123.                 m_text = v_oldText;
    124.  
    125.                 m_isCalculateSizeRequired = true;
    126.  
    127.                 return v_parsedEmoji;
    128.             }
    129.  
    130.             return false;
    131.         }
    132.  
    133.         #endregion
    134.  
    135.         #region Text Overriden Functions
    136.  
    137.         public override void SetVerticesDirty()
    138.         {
    139.             //In textmeshpro 1.4 the parameter "m_isInputParsingRequired" changed to internal, so, to dont use reflection i changed to "m_havePropertiesChanged" parameter
    140.             if (IsInputParsingRequired_Internal)
    141.             {
    142.                 m_emojiParsingRequired = m_isRichText;
    143.             }
    144.             base.SetVerticesDirty();
    145.         }
    146.  
    147.         public override void Rebuild(CanvasUpdate update)
    148.         {
    149.             if (this == null && enabled && gameObject.activeInHierarchy) return;
    150.  
    151.             if (m_emojiParsingRequired)
    152.                 ParseInputTextAndEmojiCharSequence();
    153.  
    154.             base.Rebuild(update);
    155.         }
    156.  
    157.         public override string GetParsedText()
    158.         {
    159.             if (m_emojiParsingRequired)
    160.                 ParseInputTextAndEmojiCharSequence();
    161.  
    162.             return base.GetParsedText();
    163.         }
    164.  
    165.         public override TMP_TextInfo GetTextInfo(string text)
    166.         {
    167.             TMP_EmojiSearchEngine.ParseEmojiCharSequence(spriteAsset, ref text);
    168.             return base.GetTextInfo(text);
    169.         }
    170.  
    171.         protected override Vector2 CalculatePreferredValues(float defaultFontSize, Vector2 marginSize, bool ignoreTextAutoSizing)
    172.         {
    173.             if (m_emojiParsingRequired)
    174.                 ParseInputTextAndEmojiCharSequence();
    175.  
    176.             return base.CalculatePreferredValues(defaultFontSize, marginSize, ignoreTextAutoSizing);
    177.         }
    178.  
    179.         #endregion
    180.     }
    181. }
    182.  
     
    Last edited: Nov 27, 2019
  28. Yeisonlop10

    Yeisonlop10

    Joined:
    Jan 10, 2018
    Posts:
    3
    Hi Rafael and thank you so much for your contribution. I am trying to use your project. Followed the instructions but I see no characters, emojis etc in the text field. I have been modifying the fonts colors sizes etc, but nothing happens. Is there anything else that maybe I have missed? Followed all the steps without problems. Thank you.
     
unityunity