Search Unity

Question on getPreferredValues and text wrapping

Discussion in 'UGUI & TextMesh Pro' started by john-gregory, Oct 5, 2018.

  1. john-gregory

    john-gregory

    Joined:
    Oct 28, 2013
    Posts:
    2
    I'm using TextMeshPro in a project where the program needs to have full control over where the text blocks are placed on the screen, and uses getPreferredValues to determine the width and height of a text string prior to setting the width and height of the RectTransform of the object containing the TMPro UGUI. Unfortunately, I can't use the automatic layout groups/elements offered by Unity, because I need to mimic specific layout behaviors of a previous project for backward compatibility.

    Anyway, I've gotten it mostly working the way I want, except that the getPreferredValues function does not seem to work when passing a width parameter. When I call getPreferredValues(text, 0, 0), the function correctly passes the size of the unwrapped text; similar to calling getPreferredValues(text). I wasn't able to find much in the way of documentation for getPreferredValues, but I was assuming that I can call getPreferredValues(text, width, 0) to determine the width and height of a wrapped text block by enabling wrapping and passing the fixed width as a parameter. However, the result of this call is a vertical height equal to the wrapped text, but a horizontal width equal to the unwrapped text.

    Question: Am I using getPreferredValues correctly to calculate the size of a text string within a fixed width considering wrapping?

    Note: If I change line 4079 in my version of the code
    from: "renderedWidth += m_xAdvance;"
    to: "maxXAdvance = Mathf.Max(maxXAdvance, renderedWidth + m_xAdvance); renderedWidth = 0;"
    then everything calculates as expected where the width and height returned are equal to the wrapped text size within the fixed width. (I borrowed the changes from the carriage return.)

    Based on whether I am using getPreferredValues correctly, it seems like it would be a straightforward change to fix measurement of wrapped text.

    Thanks,
    John
     
  2. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    Let me take a look as GetPreferredValues() does indeed appear to be returning incorrect values.
     
  3. DodgeRollBrent

    DodgeRollBrent

    Joined:
    Dec 15, 2012
    Posts:
    7
    Has this been fixed? If so, in which version? I'm running into the same problem.
     
  4. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    I have made several fix related to this overtime. What version of the TMP package are you using?

    Can you provide an example of the behavior you are seeing and the steps for me to reproduce it?
     
  5. mklasson

    mklasson

    Joined:
    Oct 30, 2018
    Posts:
    28
    @Stephan_B
    The same problem exists in 2.1.6.

    I notice in TMP_Text.cs that GetPreferredWidth forces CalculatePreferredValues' isWordWrappingEnabled parameter to false. Letting that be true instead and then applying john-gregory's fix seems to let GetPreferredValues(maxWidth, 0) return the correct width for wrapped text, respecting the maxWidth parameter.

    If this is not the intended result then what is the suggested way of calculating the width for wrapped text?
     
  6. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    I just made a few changes to try to improve the Preferred Values calculations.

    First, GetPreferredValues() and GetPreferredValues(string) will always returned the "Preferred Width and Height" taking into consideration if we are preserving white spaces or not. This assumes there are no restrictions on the text container width. In the event the text contains multiple lines of text with linefeeds, the preferred width will represent the longest line without wrapping.

    GetPreferredValues(string, float, float) will take into consideration the TextWrappingMode as well as width restrictions of the text containers. So although this function is called Preferred, it will be constraint by the provided width which could be confusing but I think more inline with expectations.

    Given the following:
    Code (csharp):
    1. Vector2 preferredValues = TextComponent.GetPreferredValues("AB  CDE   FG  ");
    The above returns (22.4, 4.03) in Normal and No Wrap modes where the last 2 white spaces are ignored in the calculations.

    upload_2021-5-29_14-44-25.png

    Returns (24.41, 4.03) in Preserve Whitespace and Preserve Whitespace No Wrap modes where the last 2 spaces at the end of the line are considered.

    upload_2021-5-29_14-44-47.png

    Code (csharp):
    1. Vector2 preferredValues = TextComponent.GetPreferredValues("AB  CDE   FG  ", 11, 0);
    Returns (7.58, 12.31) in Normal / No Wrap modes where this 7.58 is the width of the longest wrapped line not considering white spaces at the end of the line.
    upload_2021-5-29_14-47-54.png

    Returns (10.60, 12.31) in Preserve Whitespace / Preserve Whitespace No Wrap where this time, the white spaces at the end of the line are considered.

    upload_2021-5-29_14-49-41.png

    Let me know your thoughts.
     
  7. mklasson

    mklasson

    Joined:
    Oct 30, 2018
    Posts:
    28
    Thank you, this sounds great!

    I personally only need the penultimate case at the moment. I considered testing prefixes of the string to find out when the width exceeds my bound and then inserting manual newlines, but I'd be very happy to not have to do that :)

    Any rough estimate when this will be available?
     
  8. nindim

    nindim

    Joined:
    Jan 22, 2013
    Posts:
    130
    Hi @Stephan_B ,

    I came across this thread as I am trying to set a TextMeshPro object's rect to its preferred size within a maximum width and height.

    We are using TMPro 3.0.6 in Unity 2021.3.17

    It seems to work well for the most part using Text.GetPreferredValues(x, y), but issues arise when the text is wrapped. The returned width is always the width as though the text was not wrapped and as such the box is too wide versus the rendered text.

    upload_2023-3-19_18-0-57.png

    The point at which the text is no longer wrapped is 329.23, any value higher than that will still clamp to 329.23 as expected:

    upload_2023-3-19_18-4-16.png

    Smaller values also work as expected:

    upload_2023-3-19_18-4-46.png

    It looks like this issue could be because TMP_Text.GetPreferredWidth() always passes false for isWordWrappingEnabled into CalculatePreferredValues?

    upload_2023-3-19_18-2-40.png

    I have kind of bodged a solution for now by setting the width and height based off of GetPreferredValues, then calling
    ForceMeshUpdate and then using GetRenderedValues to set the final rect size, but this is not ideal.

    Any help would be appreciated :)
     
  9. nindim

    nindim

    Joined:
    Jan 22, 2013
    Posts:
    130
    I've also just noticed that GetPreferredValues does not seem to take into account any of the "Spacing Options (em)" set on the Text object component.

    Default setup:

    upload_2023-3-20_9-19-13.png

    With character spacing set on component:

    upload_2023-3-20_9-20-3.png

    Interestingly, if I set the character spacing using rich text tags it does work correctly. The units do differ by a factor of 10 which was unexpected given both the component and rich text tags are using em units.

    upload_2023-3-20_9-21-34.png

    Thanks a lot.
     
  10. mklasson

    mklasson

    Joined:
    Oct 30, 2018
    Posts:
    28
    @Stephan_B or anyone else in the know:
    Did this never make it into production then? And relatedly, has all work on TMP stopped? Latest stable version seems to be 3.0.6 from April 2021, and latest preview version 4.0.0-pre.2 from November 2021?

    Not sure what I was on when originally asking about it 2+ years ago, but currently using Unity 2022.3.7 and its TMP 3.0.6.
     
  11. zhuchun

    zhuchun

    Joined:
    Aug 11, 2012
    Posts:
    433
    Stephan_B transferred TMP to the UI Toolkit team a few months ago, the team is focusing on an embedded version of TMP. So it seems like updates will never be done and we're on our own now.

    So in order to do above:
    1. Make the package embedded by moving "com.unity.textmeshpro@3.0.6" from "Library/PackageCache" to "Packages" folder.
    2. Open TMP_Text.cs line #3783, replace it with
    "float preferredWidth = CalculatePreferredValues(ref fontSize, margin, false, m_enableWordWrapping).x;". This will allow the component to take word wrapping into account.
    3. Still in TMP_Text.cs, line #4522, replace it with "maxXAdvance = Mathf.Max(maxXAdvance, renderedWidth + m_xAdvance); renderedWidth = 0;" . A hack provided by john-gregory
     
    elguncumayev and andgusaandgusa like this.