Getting exact word position for text in paragraph

My goal is to place buttons over certain words in text (ie, hyperlinking).
I’m close, but I can’t seem to get the height position correct. It’s just slightly off.

I believe my problem must lie in this line?
newC.y= _Text.cachedTextGenerator.GetPreferredHeight(thisStr, tgs);

Using Regex, I grab all the pertinent links and the iterate through them to Instantiate the buttons.
This switch case handles that for me:

case "href":
    rt = container.GetComponent<RectTransform> ();
    float containerWidth = Screen.width - rt.sizeDelta.x - rt.anchoredPosition.x; //this is stretched so keep testing
    containerWidth -= container.parent.GetComponent<RectTransform> ().sizeDelta.x;
    containerWidth -= container.parent.GetComponent<RectTransform> ().anchoredPosition.x;
    containerWidth -= 20; //Text box itself
   
    CharacterInfo info = new CharacterInfo ();
    string thisStr = "";
    Vector2 newC = Vector2.zero;
   
    //get the X position of the button. My example text should produce all zeros here.
    for (int j = 0; j < _Text.text.IndexOf (match.Groups[19].Value); j++) {
        thisStr += _Text.text[j].ToString ();
        if (_Text.text[j].ToString () == "\n" || _Text.text[j].ToString () == "\r") {
            newC.x = 0;
            continue;
        }
        instance.font.RequestCharactersInTexture (_Text.text[j].ToString (), 18);
        if (instance.font.GetCharacterInfo (_Text.text[j], out info, 18)) {
            if (newC.x + info.advance > containerWidth)
                newC.x = info.advance;
            else
                newC.x += info.advance;
        }
    }

    TextGenerationSettings tgs = _Text.GetGenerationSettings (new Vector2 (containerWidth, 1000)); //arbitrary height for now

    newC.x += 10; //account for Text.text indent
    newC.y = _Text.cachedTextGenerator.GetPreferredHeight(thisStr, tgs);
    newC.y *= -1; //string down from top
   
    tmp = (GameObject)Instantiate((GameObject)Resources.Load ("Prefabs/btn_Url"), Vector3.zero, Quaternion.identity);
    tmp.transform.SetParent (container);
    rt = tmp.GetComponent<RectTransform> ();
    rt.anchoredPosition = newC;
   
    //finally let's set the button size
    newC = Vector2.zero;
    newC.x = _Text.cachedTextGenerator.GetPreferredWidth(match.Groups[19].Value, tgs);
    newC.y = _Text.cachedTextGenerator.GetPreferredHeight(match.Groups[19].Value, tgs);
    rt.sizeDelta = newC;
    break;

My example Text.text looks like this:

My Text settings:
Font Arial
Style Normal
Size 18
Line Spacing 1
Rich Text true
Alignment upper left
Wrap & Truncate
Best Fit false
Material None

And the resulting placement is attached as an image. It shows that:

  • all the buttons are the correct size
  • the initial button positions appear to be correct
  • then the positions of the text appear to severely come up short in regards to height.

Is the problem my Canvas.scaleFactor? (scale with screen size)
I’ve tried to account for it but even that doesn’t work perfectly.

newC.y *= -(1 - container.root.GetComponentInParent<Canvas> ().scaleFactor + 1);

So I got closer. An issue was definitely the CanvasScaler. However that doesn’t solve for the editor and I haven’t tested real-world devices.

                    case "href":
                        float sf = container.root.GetComponentInParent<Canvas> ().scaleFactor;

                        rt = container.GetComponent<RectTransform> ();
                        float containerWidth = 640 - rt.sizeDelta.x - rt.anchoredPosition.x; //640 = Canvas scalefactor width
                        containerWidth -= 48; //container.parent.GetComponent<RectTransform> ().sizeDelta.x;
                       
                        CharacterInfo info = new CharacterInfo ();
                        string thisStr = "";
                        Vector2 newC = Vector2.zero;
                       
                        TextGenerationSettings tgs = _Text.GetGenerationSettings (new Vector2 (containerWidth, 1000));

                        //solve for x position
                        for (int j = 0; j < _Text.text.IndexOf (match.Groups[19].Value); j++) {
                            thisStr += _Text.text[j].ToString ();
                            if (_Text.text[j].ToString () == "\n" || _Text.text[j].ToString () == "\r") {
                                newC.x = 0;
                                continue;
                            }
                            instance.font.RequestCharactersInTexture (_Text.text[j].ToString (), tgs.fontSize);
                            if (instance.font.GetCharacterInfo (_Text.text[j], out info, tgs.fontSize)) {
                                if (newC.x + info.advance > containerWidth)
                                    newC.x = info.advance;
                                else
                                    newC.x += info.advance;
                            }
                        }
                       
                        newC.x *= (1f / sf); //inverse scale
                        newC.x += 10; //account for text indent

                        //solve for y position
                        newC.y = _Text.cachedTextGenerator.GetPreferredHeight(thisStr, tgs);
                        newC.y *= -1;
                        newC.y *= (1f / sf); //inverse scale
                        newC.y += 10;
                       
                        tmp = (GameObject)Instantiate((GameObject)Resources.Load ("Prefabs/btn_Url"), Vector3.zero, Quaternion.identity);
                        tmp.transform.SetParent (container);
                        rt = tmp.GetComponent<RectTransform> ();
                        rt.anchoredPosition = newC;
                        tmp.GetComponent<buttonListenerURL> ().Url = match.Groups[3].Value.ToString ();

                        //solve button scale
                        newC = Vector2.zero;
                        newC.x = _Text.cachedTextGenerator.GetPreferredWidth(match.Groups[19].Value, tgs);
                        newC.y = _Text.cachedTextGenerator.GetPreferredHeight(match.Groups[19].Value, tgs);
                        rt.sizeDelta = newC;

                        break;

This doesn’t always solve for the scaling of the editor Game screen if that Game screen doesn’t equal the size of the device. In other words, I setup for iPhone 5, but that doesn’t fit on my monitor so the editor dynamically scales the Game Screen size to something smaller that fits.

In this case, the buttons don’t line up.

Even if I don’t get a reply to assist in this issue, I hope this code helps someone else with their project :slight_smile:

So how is it possible that my container width, the width == 500 and _Text.cachedTextGenerator.GetPreferredWidth == 558? Preferred is wider then container?

TextGenerationSettings tgs = _Text.GetGenerationSettings (new Vector2 (containerWidth, 1000));

newC.x = _Text.cachedTextGenerator.GetPreferredWidth(match.Groups[19].Value, tgs);