Search Unity

  1. Check out the Unite LA keynote for updates on the Visual Effect Editor, the FPS Sample, ECS, Unity for Film and more! Watch it now!
    Dismiss Notice
  2. The Unity Pro & Visual Studio Professional Bundle gives you the tools you need to develop faster & collaborate more efficiently. Learn more.
    Dismiss Notice
  3. Improved Prefab workflow (includes Nested Prefabs!), 2D isometric Tilemap and more! Get the 2018.3 Beta now.
    Dismiss Notice
  4. Improve your Unity skills with a certified instructor in a private, interactive classroom. Watch the overview now.
    Dismiss Notice
  5. Want to see the most recent patch releases? Take a peek at the patch release page.
    Dismiss Notice

TextMesh Pro How to parse tmpro text that contains formatting tags and remove lines

Discussion in 'Unity UI & TextMesh Pro' started by Munchy2007, Oct 15, 2018.

  1. Munchy2007

    Munchy2007

    Joined:
    Jun 16, 2013
    Posts:
    933
    Hi, I'm trying to remove the first line from the start of a tmpro text element which may contain formatting tags, but I'm struggling to work out how to account for the tags when determining the start of the 2nd line, this short example illustrates the problem.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using TMPro;
    5.  
    6. public class TestTmpro : MonoBehaviour {
    7.  
    8.     void Start () {
    9.         var tmpText = GetComponent<TextMeshProUGUI>();
    10.  
    11.         // This works and the resulting text is 'Line two'
    12.         tmpText.text = "Hello World\nLine two";
    13.         tmpText.ForceMeshUpdate();
    14.         tmpText.text = tmpText.text.Substring(tmpText.textInfo.lineInfo[1].firstVisibleCharacterIndex);
    15.  
    16.         // This doesn't work proprly, the resulting text is:-
    17.         //      00>Hello World</color>\n<color=#0055FF>Line two</color>
    18.         // whereas I'd like it to be
    19.         //      <color=#0055FF>Line two</color>
    20.         tmpText.text = "<color=#FF5500>Hello World</color>\n<color=#0055FF>Line two</color>";
    21.         tmpText.ForceMeshUpdate();
    22.         tmpText.text = tmpText.text.Substring(tmpText.textInfo.lineInfo[1].firstVisibleCharacterIndex);
    23.     }
    24. }
    It works perfectly okay when there are no formatting tags, but otherwise not. Is there a better way to approach this. Please note the length and/or existence of the formatting tags isn't known in advance.

    Many thanks.
     
  2. Julien_at_work

    Julien_at_work

    Joined:
    Aug 9, 2018
    Posts:
    10
    If the formatting tags are not wrapped around the whole string (only around each line), you may be able to split them by the newline character.
     
  3. Munchy2007

    Munchy2007

    Joined:
    Jun 16, 2013
    Posts:
    933
    Unfortunately the tags can be anywhere in the line, not necessarily at either end.

    The problem is that the tmpro.textinfo that I use to determine which lines to display only provides information about the parsed text, there's no (as far as I can tell) way to retrieve the unparsed text that represents what you can see as the line in the text element. This makes manipulating the contents of the unparsed string quite difficult and if you want to manually remove lines from the text element, how else is there to do it?

    At the moment I think that, I could get the LineInfo.firstCharIndex of the first line I want to display and then work my way backwards until I find a newline char and remove everything including and before that. Which is pretty much as Julien_at_work suggests.

    Unless anyone can suggest a more elegant solution, it looks like that's the approach I'll have to take.
     
    Last edited: Oct 15, 2018
  4. Stephan_B

    Stephan_B

    Unity Technologies

    Joined:
    Feb 26, 2017
    Posts:
    1,780
    In the CharacterInfo the index references the character in the original string. So if you iterate over the character info index vs. the string you can effectively figure out which are displayable characters vs. rich text.

    You can also take a look at the TMP_InputField which has to do this and includes a few utility functions to handle this.
     
    Munchy2007 likes this.
  5. Munchy2007

    Munchy2007

    Joined:
    Jun 16, 2013
    Posts:
    933
  6. Munchy2007

    Munchy2007

    Joined:
    Jun 16, 2013
    Posts:
    933
    Where can I find the TMP_InputField script, I only have dlls in my tmpro version?
     
  7. Stephan_B

    Stephan_B

    Unity Technologies

    Joined:
    Feb 26, 2017
    Posts:
    1,780
    I thought you were already using the version of TMP delivered via the Package Manager which includes source code.

    I guess since you only want to look at the code, just install Unity 2018.2 and create an empty project so you can browse the TMP stuff.
     
    Munchy2007 likes this.
  8. Munchy2007

    Munchy2007

    Joined:
    Jun 16, 2013
    Posts:
    933
    After messing around with this on and off for quite a while, I came to the conclusion that there was no reliable way to find the start of an unparsed line, and so include its formatting tags, by using the parsed character index returned by textInfo.lineInfo.firstCharacterIndex.

    But in a moment of clarity I realised that I could just temporarily turn of RichText formatting before I remove the line and turn it back on again. This works perfectly for what I want. I'm not sure how performant switching between no formatting and RTF formatting is, but for my purposes it doesn't cause any problems.

    In case anyone else finds it useful here's how I ended up implementing it.

    Code (CSharp):
    1.  
    2.     [SerializeField]
    3.     TextMeshProUGUI displayText;
    4.     [SerializeField]
    5.     int maxLines = 6;
    6.  
    7.     void RemoveExcessLinesFromStart()
    8.     {
    9.         displayText.richText = false;
    10.         displayText.ForceMeshUpdate();
    11.         var lineCount = displayText.textInfo.lineCount;
    12.         if (lineCount > maxLines)
    13.         {
    14.             var line = lineCount - maxLines;
    15.             displayText.text = displayText.text.Substring(displayText.textInfo.lineInfo[line].firstCharacterIndex);
    16.         }
    17.         displayText.richText = true;
    18.     }
    I'd still be interested to find out if there's a better way to do this.