TMP_TextUtilities.FindIntersectingLink is bugged on TextOverflowModes.Link

If I have multiple Text(UI) components connected to each other by TextOverflowMode → Linked, the linked text boxes will not support TMP_TextUtilities.FindIntersectingLink.
Example:

  • Text1: Contains some links and is connected to Text2 by TextOverflowMode → Linked
  • Text2: Contains some links and is connected to Text3 by TextOverflowMode → Linked
  • Text3: Contains some links and is set as Overflow

If i run FindIntersectingLink, Text1 finds the links correctly, Text2 doesn’t, Text3 does.

Cause:
When FindIntersectingLink is run on Text2, it iterates through ALL the links found, including the ones that are in Text1.

From implementation of TMP_TextUtilities: 1399
```csharp
**// Last Character of Word
if (isBeginRegion && j == linkInfo.linkTextLength - 1)
{
isBeginRegion = false;

br = rectTransform.TransformPoint(new Vector3(currentCharInfo.topRight.x, currentCharInfo.descender, 0));
tr = rectTransform.TransformPoint(new Vector3(currentCharInfo.topRight.x, currentCharInfo.ascender, 0));

// Check for Intersection
if (PointIntersectRectangle(position, bl, tl, tr, br))
    return i;

//Debug.Log("End Word Region at [" + currentCharInfo.character + "]");

}**
```

This will make PointIntersectRectangle(position, bl, tl, tr, br) to return true when bl == br and tl == tr since the point found in Text1 is invalid and will be set at the origin of the transform.

Result: whenever Text2 is hovered, it will think that the first link in Text 1has being hovered.

yes, just come across this bug too, the FindIntersectingLink() function is always returning 0 for the second box no matter which link you actually click

edit:
sort of fixed it by modifying the FindIntersectingLink code
you can exit the loop early if the character isn’t visible

edit2: still breaks if the link is split between the 2 boxes… not sure how to fix that yet (i guess it’s ok if you make sure you have 1 word links)

if (linkInfo.linkTextfirstCharacterIndex < text.firstVisibleCharacter)
      {
        continue;
      }

so extract out these 3 functions to your own file and call the FindIntersectingLink() from that script instead

public static int FindIntersectingLink(TMP_Text text, Vector3 position, Camera camera)
  {
    Transform rectTransform = text.transform;

    // Convert position into Worldspace coordinates
    ScreenPointToWorldPointInRectangle(rectTransform, position, camera, out position);

    for (int i = 0; i < text.textInfo.linkCount; i++)
    {
      TMP_LinkInfo linkInfo = text.textInfo.linkInfo[i];

      bool isBeginRegion = false;

      Vector3 bl = Vector3.zero;
      Vector3 tl = Vector3.zero;
      Vector3 br = Vector3.zero;
      Vector3 tr = Vector3.zero;

      if (linkInfo.linkTextfirstCharacterIndex < text.firstVisibleCharacter)
      {
        continue;
      }

      // Iterate through each character of the word
      for (int j = 0; j < linkInfo.linkTextLength; j++)
      {
        int characterIndex = linkInfo.linkTextfirstCharacterIndex + j;
        TMP_CharacterInfo currentCharInfo = text.textInfo.characterInfo[characterIndex];
        int currentLine = currentCharInfo.lineNumber;

        // Check if Link characters are on the current page
        if (text.overflowMode == TextOverflowModes.Page && currentCharInfo.pageNumber + 1 != text.pageToDisplay) continue;

        if (isBeginRegion == false)
        {
          isBeginRegion = true;

          bl = rectTransform.TransformPoint(new Vector3(currentCharInfo.bottomLeft.x, currentCharInfo.descender, 0));
          tl = rectTransform.TransformPoint(new Vector3(currentCharInfo.bottomLeft.x, currentCharInfo.ascender, 0));

          //Debug.Log("Start Word Region at [" + currentCharInfo.character + "]");

          // If Word is one character
          if (linkInfo.linkTextLength == 1)
          {
            isBeginRegion = false;

            br = rectTransform.TransformPoint(new Vector3(currentCharInfo.topRight.x, currentCharInfo.descender, 0));
            tr = rectTransform.TransformPoint(new Vector3(currentCharInfo.topRight.x, currentCharInfo.ascender, 0));

            // Check for Intersection
            if (PointIntersectRectangle(position, bl, tl, tr, br))
              return i;

            //Debug.Log("End Word Region at [" + currentCharInfo.character + "]");
          }
        }

        // Last Character of Word
        if (isBeginRegion && j == linkInfo.linkTextLength - 1)
        {
          isBeginRegion = false;

          br = rectTransform.TransformPoint(new Vector3(currentCharInfo.topRight.x, currentCharInfo.descender, 0));
          tr = rectTransform.TransformPoint(new Vector3(currentCharInfo.topRight.x, currentCharInfo.ascender, 0));

          // Check for Intersection
          if (PointIntersectRectangle(position, bl, tl, tr, br))
            return i;

          //Debug.Log("End Word Region at [" + currentCharInfo.character + "]");
        }
        // If Word is split on more than one line.
        else if (isBeginRegion && currentLine != text.textInfo.characterInfo[characterIndex + 1].lineNumber)
        {
          isBeginRegion = false;

          br = rectTransform.TransformPoint(new Vector3(currentCharInfo.topRight.x, currentCharInfo.descender, 0));
          tr = rectTransform.TransformPoint(new Vector3(currentCharInfo.topRight.x, currentCharInfo.ascender, 0));

          // Check for Intersection
          if (PointIntersectRectangle(position, bl, tl, tr, br))
            return i;

          //Debug.Log("End Word Region at [" + currentCharInfo.character + "]");
        }
      }

      //Debug.Log("Word at Index: " + i + " is located at (" + bl + ", " + tl + ", " + tr + ", " + br + ").");

    }

    return -1;
  }

  public static bool ScreenPointToWorldPointInRectangle(Transform transform, Vector2 screenPoint, Camera cam, out Vector3 worldPoint)
  {
    worldPoint = (Vector3)Vector2.zero;
    Ray ray = RectTransformUtility.ScreenPointToRay(cam, screenPoint);

    float enter;
    if (!new Plane(transform.rotation * Vector3.back, transform.position).Raycast(ray, out enter))
      return false;

    worldPoint = ray.GetPoint(enter);

    return true;
  }

  private static bool PointIntersectRectangle(Vector3 m, Vector3 a, Vector3 b, Vector3 c, Vector3 d)
  {
    Vector3 ab = b - a;
    Vector3 am = m - a;
    Vector3 bc = c - b;
    Vector3 bm = m - b;

    float abamDot = Vector3.Dot(ab, am);
    float bcbmDot = Vector3.Dot(bc, bm);

    return 0 <= abamDot && abamDot <= Vector3.Dot(ab, ab) && 0 <= bcbmDot && bcbmDot <= Vector3.Dot(bc, bc);
  }
2 Likes

You saved me!!!