I want my Text to always be centered (vertical and horizontal) in a defined rectangle and never grow bigger than the defined horizontal borders.
The following screenshot visualizes the desired result:
My text is left-aligned, and the longest line of text defines the horizontal center point of the text.
The vertical center point is defined by the total amount of lines, or the height of the Rect-Transform.
To achieve this, I created an Image and added a Vertical Layout Group, and set Child Alignment to Middle Center and ticked Control Child SizeWidth and Height. (Since I only use the Layout Group to position and resize its one child (the text), I could also use a Horizontal Layout Group).
Then I added a TMP-Text as a Child of that Image. Done.
Limitation of my current solution:
The expected behavior only occurs when I do a manual line-break.
The problem is, when I use an auto-line-break (Wrapping set to Enabled), the Rect-Transform of the text grows to its maximum size, and does the line-break.
Now the text is not centered anymore. The distance between the word “text” and the right border is way bigger than the distance between the left border and the word “This”.
I would expect that the text would be tightly surrounded by the Rect-Transform, as seen in the first screenshot.
How can I set up my left-aligned text to always be centered within a defined rectangle while also using auto-line-breaks?
I found a way to get very close to my desired solution.
Since I don’t know if this is the ideal way of doing it, I will describe my current setup briefly and then describe the issue that I run into.
I create three Objects in my Hierarchy:
A TMP-Text Object (wrapping enabled) which is the Child of an other GameObject (lets call it LayoutParent) with a Vertical Layout Group (Child Alignment*: Middle Center*, Control Child Size: Width and Height). LayoutParent is the Child of the “TextBox-Holder”. This setup allows me to safely change the local position of LayoutParent with the following method.
Whenever I assign a new text to my Text Object, I force-update the Mesh and my Layout-Group. Then I get the textBounds and assign those to the local position of LayoutParent. Like so:
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class DialogueTextFormatter : MonoBehaviour
{
[SerializeField] TMP_Text tmpText;
[SerializeField] RectTransform layoutParentRt;
public void AssignNewString(string s)
{
tmpText.text = s;
LayoutRebuilder.ForceRebuildLayoutImmediate(layoutParentRt);
tmpText.ForceMeshUpdate();
Bounds bounds = tmpText.textBounds;
layoutParentRt.localPosition = -bounds.center;
}
}
Pros:
Thanks to the Layout-Group, the max width of the text is restricted and Wrapping kicks in
The longest line of text will be horizontally centered, even with Wrapping enabled
Cons:
The text is not perfectly centered
I don’t know why that is, but regardless of if I use tmpText.bounds or tmpText.textBounds, not all text will always be perfectly centered. I noticed that the x-value of the Center value of the bounds fluctuates. I saw values from -0.4 (when character “w” is at first position), and +1.4 (when character “!” is at first position).
Here is a very simple comparison. The text “!” is not exactly in the middle. “.textBounds.center.x” is 1.4. As a result, the text is too much to the left.
So in addition to the original question, I now want to ask: Is this the right attempt of solving this? And if so, how do I manage to get the text perfectly centered.
This is by design as layout components check the Preferred Width which in the case of text is the length of the longest line without breaking it. Note that Preferred Width is calculated with Word Wrapping disabled.
Text alignment is based on the text metrics and not the geometry of characters. The following information should help you get a better understanding of this.
A Character is assigned a Unicode code point and references a glyph which is the visual representation of this character. You can look at this relationship between characters and glyphs by looking at the Character and Glyph tables any font assets.
Glyphs contains a GlyphRect and Glyph Metrics.
The GlyphRect defines the position of the glyph in the atlas texture.
The GlyphMetrics defines the placement / positioning / layout of the glyph in a line of text. These metrics are defined by the designer of the font.
For instance, assuming we were placing the first glyph on a line of text. The drawing position would be at x = 0 and on the baseline which is y = 0. These metrics go back to the days of plotters and define the position / placement of the pen to draw the glyph. From this (0, 0) the pen would then be moved / offset by the X Bearing and Y Bearing moving the pen to the top left where it would begin to draw the glyph one line at a time using the width and height metrics.
Using the TMP_TextInfoDebugTool.cs which is included in the TMP Examples & Extras we can visualize these metrics including the Origin and Advance of a glyph as seen below.
In the above example using LiberationSans where the text is left aligned, you can see the origin of the “i” which is at x = 0. The pen is then offset by the X Bearing which is a positive value for this glyph and then also offset by the Y Bearing which is our top left corner and position of the top left vertex. From this position we add the width and we have to top right vertex. Subtract the height and we have our bottom left and right vertex.
We will repeat this process for the next glyph moving the pen to the advance of our first glyph which will now become the origin of our second glyph.
Looking at the letter “i” which has a positive X Bearing, we can see the “i” is inside the RectTransform. By contrast, the letter “j” on the second line has a negative X Bearing resulting in the bottom part of the glyph being outside the RectTransform. This looks weird but as you can see in the above image, these X Bearings were defined as such to have their Stem align vertically.
In the next image where the text is center aligned, you can see each line is centered between the Origin and Max Advance of each line.
So back to your “!” and “W”, the alignment of these glyphs is related to their X Bearing where it looks like the text bounds are incorrect as the x min = 0 instead of the Origin of the left most glyph as seen below.
The teal box represents the text bounds which are incorrect in the above image as they should be from Origin to Advance.
To correct this issue, make the following change in the TMP_Text.cs file in the GetTextBounds() function which should be done in the TMP package contained in the Global Package Cache.