How would I find the screen position of a letter from a textlabel?
For example, positioning a frame over the “e” (shown below)
How would I find the screen position of a letter from a textlabel?
For example, positioning a frame over the “e” (shown below)
It’s not really possible since text characters are different pixel sizes and AFAIK Roblox doesn’t have advanced UI support/pixel detection.
Assuming it’s a single line of horizontal text, you could get the X position of the desired letter. You’d have to use AutomaticSize with the preceding characters of the position you want.
For Example:
If you wanted the X position of “h”, you could make an AutomaticSized (X) TextLabel with “hello t” as the text (with the same text size), which you can then read the AbsoluteSize.X which will be the X position of the next letter (h).
You would still need to keep the new overlaying frame the same size and position as the Y axis since there isn’t really a way to detect the Y position/size AFAIK. The Y size, even with AbsoluteSize.Y, is still the same height as the tallest character in the font. The overlaying frame would give you a highlighter effect
.
hello th
ere
This is something missing that annoys me ALOT, I really wish roblox implemented a function to allow us to get the position of characters in text
I totally agree, this should definitely be a built in feature. I’ve actually totally forgotten about this topic and scrapped what I was working on. Today I’m going to try to find another way to do this and I’ll update the post if I find a way
I made module that can help you to solve this problem
here it is:
local module = {}
function module.GetData(TextBox)
local TextService = game:GetService("TextService")
local Height = 0
local function CreateArray(box)
local tb = Instance.new("TextBox")
tb.Parent = script
tb.FontFace = box.FontFace
if box.TextScaled == true then
tb.TextSize = box.TextBounds.Y
else
tb.TextSize = box.TextSize
end
Height = tb.TextSize
tb.TextWrapped = false
local Array = {}
local symbols = box.Text
Array = {}
for i = 1,string.len(symbols),1 do
local char = string.sub(symbols,i,i)
tb.Text = char
Array[char] = {X = tb.TextBounds.X}
end
tb.Text = string.char(32)
Array[string.char(32)] = {X = tb.TextBounds.X}
tb:Destroy()
return Array
end
local function GetBound(text,Array)
local Bound = 0
for i = 1,string.len(text),1 do
local char = string.sub(text,i,i)
local xSize = Array[char]
if xSize then
Bound +=xSize.X
end
end
return Bound
end
local function GetCharData(Text,pos,BoundsX,Array)
local Lines = GetBound(Text,Array)/TextBox.AbsoluteSize.X
if Lines %1 ~= 0 then
Lines -= Lines% 1
Lines += 1
end
local LineHeight = TextBox.LineHeight * Height
local starterPos
if TextBox.TextWrapped and Lines >=2 then
starterPos = pos - UDim2.new(0,TextBox.AbsoluteSize.X/2,0,TextBox.TextBounds.Y/2 - LineHeight * 0.5)
else
starterPos = UDim2.new(0,TextBox.AbsolutePosition.X + TextBox.AbsoluteSize.X / 2,0,TextBox.AbsolutePosition.Y + TextBox.AbsoluteSize.Y / 2) - UDim2.new(0,(BoundsX /2) ,0,0)
end
local Spacings = {}
local Inc = 0
for i = 1, string.len(Text),1 do
local char = string.sub(Text,i,i)
local xSize = Array[char]
if xSize then
table.insert(Spacings,{character = char,position = starterPos,size = UDim2.new(0,xSize.X,0,Height)})
starterPos += UDim2.new(0, xSize.X,0,0)
Inc += xSize.X
end
if Inc >= BoundsX and TextBox.TextWrapped == true then
break
end
end
return Spacings
end
local Array = CreateArray(TextBox)
local Data = GetCharData(TextBox.Text,TextBox.Position,TextBox.TextBounds.X,Array)
return Data
end
return module
How to use?
module.GetData(TextBox)
returns array of data for each letter in TextBox.Text
example of data for textbox, which texbox.Text is “H”
data = {
{
character = 'H',
size = UDim2.new(0,9,0,8),
position = UDim2.new(0,939,0,468)
}
}
Code example:
local module = nil -- Enter here your path to module
local ScreenGui = Instance.new("ScreenGui") -- creates ScreenGui
ScreenGui.Parent = game:GetService("StarterGui")
local tb = Instance.new("TextBox") -- creates TextBox
tb.Size = UDim2.new(0.5,0,0.5,0)
tb.AnchorPoint = Vector2.new(0.5,0.5)
tb.Position = UDim2.new(0.5,0,0.5,0)
tb.Text = "Hooray"
tb.Parent = ScreenGui
local data = module.GetData(tb)
for i,v in ipairs(data) do
print(v.character,v.size,v.position)
end
Output:
Yo, does this only work if you aren’t using TextWrapped? Are there any rules to using it? I’ve been trying to get it to work and it doesn’t work quite right (even if TextWrapped is off).
Note that this module will only work on simple UIs like this:
Whenever I try to use it on more complex UI it just gives wrong position:
Also would be nice if module had VisualizeData
method (like on your example), would be easier to debug any issues like mine.
I will try to rewrite this module, so it will be more usable on large projects and reply to this message when I will finish it.
Forgot a bit about this thread, but here’s a new module:
local characterInfo = {}
--[[
|- Name:
TextObjects Characters Info
|- Author(s):
^ kercig: Wrote original module
^ frostxoff: Slightly rewrote module
|- Description:
Get characters data from TextObject (TextBoxes, TextLabels, TextButtons)
|- Methods:
- characterInfo:GetDataFromTextObject(TextBox: TextBox | TextLabel | TextButton): {characterdata}
^ Returns an array of characterdata for each letter in TextBox.Text
- characterInfo:VisualizeCharacterData(data: {characterdata}, gui: ScreenGui)
^ Visualizes characters data inside `gui`
]]--
local Debris = game:GetService("Debris")
export type characterdata = {
character: string,
size: UDim2,
position: UDim2
}
function characterInfo:VisualizeCharacterData(data: {characterdata}, gui: ScreenGui, deleteafter: number | nil): nil
for i, cdata: characterdata in pairs(data) do
local newFrame = Instance.new("Frame")
newFrame.Transparency = .75
newFrame.Position = cdata.position
newFrame.Size = cdata.size
newFrame.Name = cdata.character
newFrame.Parent = gui
if deleteafter then
Debris:AddItem(newFrame, deleteafter)
end
end
end
function characterInfo:GetDataFromTextObject(TextObject: TextBox | TextLabel | TextButton): {characterdata}
local Height = 0
local function CreateArray(box)
local tb = Instance.new("TextBox")
tb.Parent = script
tb.FontFace = box.FontFace
if box.TextScaled == true then
tb.TextSize = box.TextBounds.Y
else
tb.TextSize = box.TextSize
end
Height = tb.TextSize
tb.TextWrapped = false
local Array = {}
local symbols = box.Text
Array = {}
for i = 1,string.len(symbols),1 do
local char = string.sub(symbols,i,i)
tb.Text = char
Array[char] = {X = tb.TextBounds.X}
end
tb.Text = string.char(32)
Array[string.char(32)] = {X = tb.TextBounds.X}
tb:Destroy()
return Array
end
local function GetBound(text,Array)
local Bound = 0
for i = 1,string.len(text),1 do
local char = string.sub(text,i,i)
local xSize = Array[char]
if xSize then
Bound += xSize.X
end
end
return Bound
end
local function GetCharData(Text,pos,BoundsX,Array)
local Lines = GetBound(Text,Array)/TextObject.AbsoluteSize.X
if Lines %1 ~= 0 then
Lines -= Lines% 1
Lines += 1
end
local LineHeight = TextObject.LineHeight * Height
local starterPos
if TextObject.TextWrapped and Lines >=2 then
starterPos = pos - UDim2.new(0,TextObject.AbsoluteSize.X/2,0,TextObject.TextBounds.Y/2 - LineHeight * 0.5)
else
local textxalignment = TextObject.TextXAlignment
local textyalignment = TextObject.TextYAlignment
local xPos = TextObject.AbsolutePosition.X + TextObject.AbsoluteSize.X/2 - TextObject.TextBounds.X/2
if textxalignment == Enum.TextXAlignment.Left then
xPos = TextObject.AbsolutePosition.X
elseif textxalignment == Enum.TextXAlignment.Right then
xPos = TextObject.AbsolutePosition.X + TextObject.AbsoluteSize.X - TextObject.TextBounds.X
end
local yPos = TextObject.AbsolutePosition.Y + TextObject.AbsoluteSize.Y/2 - TextObject.TextBounds.Y/2
if textyalignment == Enum.TextYAlignment.Top then
yPos = TextObject.AbsolutePosition.Y
elseif textyalignment == Enum.TextYAlignment.Bottom then
yPos = TextObject.AbsolutePosition.Y + TextObject.AbsoluteSize.Y - TextObject.TextBounds.Y
end
starterPos = UDim2.new(0, xPos, 0, yPos)
end
local Spacings = {}
local Inc = 0
for i = 1, string.len(Text),1 do
local char = string.sub(Text,i,i)
local xSize = Array[char]
if xSize then
table.insert(Spacings,{character = char,position = starterPos,size = UDim2.new(0,xSize.X,0,Height)})
starterPos += UDim2.new(0, xSize.X,0,0)
Inc += xSize.X
end
if Inc >= BoundsX and TextObject.TextWrapped == true then
break
end
end
return Spacings
end
local Array = CreateArray(TextObject)
local Data = GetCharData(TextObject.Text,TextObject.Position,TextObject.TextBounds.X,Array)
return Data
end
return characterInfo
Now it supports TextXAlignment, TextYAlignment, uses TextObject (TextBoxes, TextLabels, TextButtons) instead of TextBox
and has VisualizeCharacterData
method, make sure that both visualization GUI and GUI that have your TextObject have same IgnoreGuiInset
property value.