···11+MIT License
22+33+Copyright (c) 2025 ClaytonTDM
44+55+Permission is hereby granted, free of charge, to any person obtaining a copy
66+of this software and associated documentation files (the "Software"), to deal
77+in the Software without restriction, including without limitation the rights
88+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
99+copies of the Software, and to permit persons to whom the Software is
1010+furnished to do so, subject to the following conditions:
1111+1212+The above copyright notice and this permission notice shall be included in all
1313+copies or substantial portions of the Software.
1414+1515+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1616+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1717+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1818+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1919+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2020+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2121+SOFTWARE.
+127
Modules/TextTransform.luau
···11+--!optimize 2
22+--!strict
33+44+local module = {}
55+66+local TextService = game:GetService("TextService")
77+88+local X_ALIGNMENT_MAP = {
99+ [Enum.TextXAlignment.Left] = Enum.HorizontalAlignment.Left,
1010+ [Enum.TextXAlignment.Center] = Enum.HorizontalAlignment.Center,
1111+ [Enum.TextXAlignment.Right] = Enum.HorizontalAlignment.Right,
1212+}
1313+local Y_ALIGNMENT_MAP = {
1414+ [Enum.TextYAlignment.Top] = Enum.VerticalAlignment.Top,
1515+ [Enum.TextYAlignment.Center] = Enum.VerticalAlignment.Center,
1616+ [Enum.TextYAlignment.Bottom] = Enum.VerticalAlignment.Bottom,
1717+}
1818+1919+-- Transforms a <code>TextLabel</code> into a <code>Frame</code> with many children for easy manipulation of letters. Letter index is stored in <code>LayoutOrder</code> for each <code>Frame</code>.
2020+function module.TransformText(labelToTransform: TextLabel): Frame
2121+ local originalText = labelToTransform.Text
2222+ local originalSize = labelToTransform.Size
2323+ local originalPosition = labelToTransform.Position
2424+ local originalAnchorPoint = labelToTransform.AnchorPoint
2525+ local originalRotation = labelToTransform.Rotation
2626+ local originalParent = labelToTransform.Parent
2727+ local originalName = labelToTransform.Name
2828+ local originalFontFace = labelToTransform.FontFace
2929+ local originalTextSize = labelToTransform.TextSize
3030+ local originalTextColor = labelToTransform.TextColor3
3131+ local originalTextTransparency = labelToTransform.TextTransparency
3232+ local originalTextStrokeColor = labelToTransform.TextStrokeColor3
3333+ local originalTextStrokeTransparency =
3434+ labelToTransform.TextStrokeTransparency
3535+ local originalXAlignment = labelToTransform.TextXAlignment
3636+ local originalYAlignment = labelToTransform.TextYAlignment
3737+ local originalBackgroundColor3 = labelToTransform.BackgroundColor3
3838+ local originalBackgroundTransparency = labelToTransform.BackgroundTransparency
3939+4040+ local listFrame = Instance.new("Frame")
4141+ listFrame.Name = originalName
4242+ listFrame.Size = originalSize
4343+ listFrame.Position = originalPosition
4444+ listFrame.AnchorPoint = originalAnchorPoint
4545+ listFrame.Rotation = originalRotation
4646+ listFrame.BorderSizePixel = 0
4747+ listFrame.BackgroundTransparency = 1
4848+ listFrame.ClipsDescendants = true
4949+ listFrame.BackgroundColor3 = originalBackgroundColor3
5050+ listFrame.BackgroundTransparency = originalBackgroundTransparency
5151+5252+ local layout = Instance.new("UIListLayout")
5353+ layout.FillDirection = Enum.FillDirection.Horizontal
5454+ layout.Wraps = true
5555+ layout.HorizontalAlignment =
5656+ X_ALIGNMENT_MAP[originalXAlignment] or Enum.HorizontalAlignment.Left
5757+ layout.VerticalAlignment =
5858+ Y_ALIGNMENT_MAP[originalYAlignment] or Enum.VerticalAlignment.Top
5959+ layout.SortOrder = Enum.SortOrder.LayoutOrder
6060+ layout.Parent = listFrame -- Parent layout immediately
6161+6262+ local spaceParams = Instance.new("GetTextBoundsParams")
6363+ spaceParams.Font = originalFontFace
6464+ spaceParams.Size = originalTextSize
6565+ spaceParams.Text = " " -- Single space character
6666+ local spaceSize: Vector2 = TextService:GetTextBoundsAsync(spaceParams)
6767+6868+ local charParams = Instance.new("GetTextBoundsParams")
6969+ charParams.Font = originalFontFace
7070+ charParams.Size = originalTextSize
7171+7272+ local textLength = utf8.len(originalText)
7373+ local childrenToParent = table.create(textLength) -- Pre-allocate table
7474+ local childIndex = 0
7575+7676+ for char in string.gmatch(originalText, ".") do
7777+ local charSize: Vector2
7878+ local isWhitespace = string.match(char, "%s")
7979+8080+ if isWhitespace then
8181+ charSize = spaceSize
8282+ else
8383+ charParams.Text = char
8484+ charSize = TextService:GetTextBoundsAsync(charParams)
8585+ end
8686+8787+ local textContainer = Instance.new("Frame")
8888+ textContainer.Name = char
8989+ textContainer.BackgroundTransparency = 1
9090+ textContainer.BorderSizePixel = 0
9191+ textContainer.Size = UDim2.fromOffset(charSize.X, charSize.Y)
9292+ textContainer.LayoutOrder = childIndex
9393+9494+ if not isWhitespace then
9595+ local charLabel = Instance.new("TextLabel")
9696+ charLabel.FontFace = originalFontFace
9797+ charLabel.TextSize = originalTextSize
9898+ charLabel.TextColor3 = originalTextColor
9999+ charLabel.TextTransparency = originalTextTransparency
100100+ charLabel.TextStrokeColor3 = originalTextStrokeColor
101101+ charLabel.TextStrokeTransparency =
102102+ originalTextStrokeTransparency
103103+ charLabel.BackgroundTransparency = 1
104104+ charLabel.Size = UDim2.fromScale(1, 1)
105105+ charLabel.Text = char
106106+ charLabel.TextXAlignment = originalXAlignment
107107+ charLabel.TextYAlignment = Enum.TextYAlignment.Center
108108+ charLabel.Parent = textContainer
109109+ end
110110+111111+ childIndex += 1
112112+ childrenToParent[childIndex] = textContainer
113113+ end
114114+115115+ for i = 1, childIndex do
116116+ childrenToParent[i].Parent = listFrame
117117+ end
118118+119119+ table.clear(childrenToParent)
120120+121121+ listFrame.Parent = originalParent
122122+ labelToTransform:Destroy()
123123+124124+ return listFrame
125125+end
126126+127127+return module
+12
README.md
···11+# Roblox Modules
22+33+General purpose modules for Roblox development. These modules are designed to be reusable and can be easily integrated into your Roblox projects.
44+55+- TextTransform
66+ - Transforms a `TextLabel` into a `Frame` with many children for easy manipulation of letters. Letter index is stored in `LayoutOrder` for each `Frame`.
77+88+---
99+1010+## Building
1111+1212+This project was built using [Argon](https://argon.wiki/), which is similar to Rojo. See [Argon](https://argon.wiki/) for more information on how to install and use it.