const function StripRichText(Text: string): string
return (Text:gsub("<[^>]->", ""))
end
const function MeasureText(Text: string, TextSize: number): number
const params = Instance.new("GetTextBoundsParams")
params.Text = Text
params.Size = TextSize
params.Width = math.huge
const bounds = TextService:GetTextBoundsAsync(params)
params:Destroy()
return bounds.X
end
const function PlaceLogoInBrackets(TextLabel: TextLabel, LogoID: string)
const Logo: ImageLabel = TextLabel:FindFirstChild("Logo")
if not Logo then
return
end
const TextSize = TextLabel.TextSize
const fullText = StripRichText(TextLabel.Text)
const totalWidth = MeasureText(fullText, TextSize)
const placeholderCenterX = -(totalWidth / 10) -- this is where the X position of the logo change
Logo.AnchorPoint = Vector2.new(0.5, 0.5)
Logo.Image = LogoID
Logo.Position = UDim2.new(.5, placeholderCenterX, .5, 0)
Logo.Visible = true
end
totalWidth / 10 is just a magic number. It scales off the length of the whole string, so it only lines up for some names and drifts for anything longer or shorter. Nothing about it actually points at where the brackets are.
Just find the brackets and drop the logo in the middle of the gap:
local TextService = game:GetService("TextService")
local function StripRichText(text: string): string
return (text:gsub("<[^>]->", ""))
end
local function MeasureText(text: string, textSize: number): number
local params = Instance.new("GetTextBoundsParams")
params.Text = text
params.Size = textSize
params.Width = math.huge
local bounds = TextService:GetTextBoundsAsync(params)
params:Destroy()
return bounds.X
end
local function PlaceLogoInBrackets(label: TextLabel, logoId: string)
local logo = label:FindFirstChild("Logo") :: ImageLabel?
if not logo then
return
end
local textSize = label.TextSize
local fullText = StripRichText(label.Text)
local openIndex = fullText:find("%[") -- % because [ ] are pattern specials
local closeIndex = fullText:find("%]")
if not openIndex or not closeIndex then
logo.Visible = false
return
end
local totalWidth = MeasureText(fullText, textSize)
local openEdge = MeasureText(fullText:sub(1, openIndex), textSize)
local closeEdge = MeasureText(fullText:sub(1, closeIndex), textSize) - MeasureText("]", textSize)
local gapCenter = (openEdge + closeEdge) / 2
local offsetX
if label.TextXAlignment == Enum.TextXAlignment.Left then
offsetX = gapCenter - label.AbsoluteSize.X / 2
elseif label.TextXAlignment == Enum.TextXAlignment.Right then
offsetX = gapCenter - totalWidth + label.AbsoluteSize.X / 2
else
offsetX = gapCenter - totalWidth / 2
end
logo.AnchorPoint = Vector2.new(0.5, 0.5)
logo.Image = logoId
logo.Position = UDim2.new(0.5, offsetX, 0.5, 0)
logo.Visible = true
end
I’ve looked into it again and I found that the drift isn’t the bracket math, it’s the measuring. GetTextBoundsAsync uses its own default font instead of the one your label renders in, so the widths come back wrong and the logo slides off. Same thing happens if the label has TextScaled on, since TextSize isn’t the real size at that point.
Measure in the label’s own font and rescale to the width it’s actually drawing (TextBounds):
local TextService = game:GetService("TextService")
local function StripRichText(text: string): string
return (text:gsub("<[^>]->", ""))
end
local function MeasureText(text: string, font: Font, size: number): number
local params = Instance.new("GetTextBoundsParams")
params.Text = text
params.Font = font
params.Size = size
params.Width = math.huge
local bounds = TextService:GetTextBoundsAsync(params)
params:Destroy()
return bounds.X
end
local function PlaceLogoInBrackets(label: TextLabel, logoId: string)
local logo = label:FindFirstChild("Logo") :: ImageLabel?
if not logo then
return
end
local fullText = StripRichText(label.Text)
local openIndex = fullText:find("%[")
local closeIndex = fullText:find("%]")
if not openIndex or not closeIndex then
logo.Visible = false
return
end
local font = label.FontFace
local size = label.TextSize
-- measure at TextSize, then rescale to the real rendered width
-- (fixes wrong font and TextScaled labels)
local measured = MeasureText(fullText, font, size)
local totalWidth = label.TextBounds.X
local scale = totalWidth / measured
local openEdge = MeasureText(fullText:sub(1, openIndex), font, size) * scale
local closeEdge = (MeasureText(fullText:sub(1, closeIndex), font, size) - MeasureText("]", font, size)) * scale
local gapCenter = (openEdge + closeEdge) / 2
local offsetX
if label.TextXAlignment == Enum.TextXAlignment.Left then
offsetX = gapCenter - label.AbsoluteSize.X / 2
elseif label.TextXAlignment == Enum.TextXAlignment.Right then
offsetX = gapCenter - totalWidth + label.AbsoluteSize.X / 2
else
offsetX = gapCenter - totalWidth / 2
end
logo.AnchorPoint = Vector2.new(0.5, 0.5)
logo.Image = logoId
logo.Position = UDim2.new(0.5, offsetX, 0.5, 0)
logo.Visible = true
end
One catch: TextBounds is only right after the label rendered a frame. If you call this straight after setting .Text, throw a task.wait() in front first.