Search code examples
c#unity-game-engineunity-editor

Unity How to get get a characters width properly


I am attempting to create a script that when a line of text would be approaching the width of the line it would attempt to wrap the text properly (ie. if the character is not '-' or ' ' then add a hyphen between letters of a word (like how word editing software does it) but when I attempt to run it a bunch of my characters disappear.

This is the text that I am testing with, "An Aberration has a bizarre anatomy, strange abilities, an alien mindset, or any combination of the three."

but these are the results of my test script

"Found with info (' ','e','r','a','t','i','o','h','s','l','d')" "Found without info ('A','n','b','z','m','y',',','g','c','f','.')" "Didnt Find ()"

and the output from compiling the text using only the characters that have available info is " erratio has a iarre aato strae ailities a alie idset or a oiatio o the three"

and here is my testing script

public class Test : MonoBehaviour
{
    [SerializeField] 
    private Type m_Type; 
    // Not System.Type, is a custom class

    [SerializeField] 
    private Font m_Font; 
    // Set to Arial for testing.

    private List<string> m_FoundWithInfo;
    private List<string> m_FoundWithoutInfo;
    private List<string> m_DidntFind;
    private void Awake()
    {
        m_FoundWithInfo = new List<string>();
        m_FoundWithoutInfo = new List<string>();
        m_DidntFind = new List<string>();
        foreach (char c in m_Type.GetDescription())
        {
            if (m_Font.HasCharacter(c))
            {
                if (m_Font.GetCharacterInfo(c, 
                    out CharacterInfo info, 14, FontStyle.Normal))
                {
                    if (!m_FoundWithInfo.Contains($"'{c}'"))
                    {
                        m_FoundWithInfo.Add($"'{c}'");
                    }
                }
                else
                {
                    if (!m_FoundWithoutInfo.Contains($"'{c}'"))
                    {
                        m_FoundWithoutInfo.Add($"'{c}'");
                    }
                }
            }
            else
            {
                if (!m_DidntFind.Contains($"'{c}'"))
                {
                    m_DidntFind.Add($"'{c}'");
                }
            }
        }
        Debug.Log($"Found with info ({string.Join(",", m_FoundWithInfo)})");
        Debug.Log($"Found without info ({string.Join(",", m_FoundWithoutInfo)})");
        Debug.Log($"Didnt Find ({string.Join(",", m_DidntFind)})");
    }
}

How can I make this work so that the returned text is otherwise identical to the original?

To clarify the Test script only checks to see what necessary characters don't have any CharacterInfo associated with them.


Solution

  • Getting character info

    Unity font management operates on textures to render characters, and needs to be informed what characters to load in order to add them to a font texture, before you can use them while rendering text. Unless you request loading characters outside your example, having characters that exist in a font but don't have info available are due to not having them loaded into a font texture when you query font for their info.

    Font.HasCharacter checks if a font you use has character defined - if it returns false, that character doesn't exist in a font and can't be loaded to be used. Example would be checking a non-latin symbol in font that covers only latin characters. Font.HasCharacter returning true means you can load that character to a font texture.

    Font.GetCharacterInfo get information about a symbol from currently loaded font texture - so there is a possibility that loaded font may have a character available, but not loaded; this will cause Font.GetCharacterInfo to return false and make character not render if your render text manually. To work around that, you need to request Unity to load characters you will need by using RequestCharactersInTexture - documentation also contains an example on how to handle manual text rendering including font texture updates. Until you have a character loaded, you won't be able to get its font info, since it doesn't exist in currently used Font texture. When calling RequestCharactersInTexture, pass all characters that occur in your text regardless if they're loaded or not - that way you make sure Font won't unload a character that was previously loaded when loading new ones.

    Determining line breaks

    When working on a solution to determine where to put line breaks, you may need to take kerning into account - depending on surrounding characters, font may want to use different distance between characters or sometimes even use different glyphs. If you render text manually, make sure to have consistent handling of kerning between calculating linebreaks - no kerning is a good strategy, as long as font you're using doesn't depend heavily on kerning. If you want to support kerning, you should work on substrings instead of single characters and try to find best points of introducing line break for a whole line - width of a line may be different from sum of all character widths that occur in said line.