Display text on multiple lines using multiple Guis

Hello there,

I have a pretty vague question I’m not so sure how to explain. I’ll try my best…

I have a frame which contains 4 labels.
Each label is a new line to display.
What would be the most efficient way possible to make my string disperse on these lines of needed?

Here’s the context:
I have an array containing Drinks with their instructions

["Cappuccino"] = {"Take a cup and place it into a Regular machine. Then place that result into a Decaf machine", game.ReplicatedStorage.FoodsAndDrinks.Cup, 2};

I have made the first value the string, the second one is the item displayed on the Viewport and the third would be the amount of lines it takes.

Sample of code so far
local function CreateItemFrame(ItemName)
	local Frame = SampleFrame:Clone()
	local Viewport = Frame.Picture.ViewportFrame
	
	local item = Recipes[ItemName][2]:Clone()
	item.Parent = Viewport
	
	local camera = Instance.new("Camera")
	Viewport.CurrentCamera = camera
	camera.Parent = Viewport
	
	camera.CFrame = item["Handle"].CFrame * CFrame.new(0, 0, 2)
	
	Frame.LineOne.TextLabel.Text = Recipes[ItemName][1]
	Frame.Picture.TextLabel.Text = ItemName
	
	Frame.Parent = script.Parent.Recipes.ScrollingFrame
end

Example of how the Gui looks
image

Anybody have any idea on how to achieve this?
Also feel free to ask questions if I have explained vaguely.

The function getLineStrings will give you a table that contains text for each line. The number of lines is the number of values in the table returned by the function. You can use the # operator to get that number. I put some information as comments to the code. I’m not 100% sure if everything works properly, but based on my tests, it seems to work.

Explanation of how the function works:
For each line the function does this. First, it gets a substring of the whole string. This substring starts from the character that comes after the character at the end of the latest line. The number of characters in this substring is the calculated average character number per line. Then it modifies this substring so that fits in the given X size as well as possible. It makes it shorter, if necessary, or longer, if possible.

After that it uses the cutLine function above it. CutLine makes sure that the line will have a proper end. For example, it makes sure that if there’s a word like ‘result’, in the whole string and the main function cuts it so that there’s ‘res’ in the end of the line, cutLine will change this to ‘re-’ . Then there will be ‘sult’ at the start of the next line. If it can’t find a proper way to cut a word, it’ll remove the word completely from that line and the word will be in the next line.

After using the cutLine function once, the main functions checks that there isn’t a whitespace character starting the next line. Tab is an exeption, it accepts that, but if there’s some other whitespace character, then the function will use the cutLine function again. I did this so that lines won’t begin weirdly because of there being a space as the first character. The tab exeption might not really make sense, though because you probably wouldn’t use it anyway, so maybe it wasn’t a very good addition.

After doing all this, the main function continues to the next line.

local TextService = game:GetService("TextService")

-- math.huge so that these values won't have effect on the value given by getTextSize
local frameSize = Vector2.new(math.huge, math.huge)

local vowels = {"a", "e", "i", "o", "u", "y", "å", "ä", "ö"}
local consonants = {"b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "n", "p", "q", "r", "s", "t", "v", "w", "x", "z"}


local function cutLine(s, lastCharNum)
	if s:sub(1, 1):match("%s") then
		warn("line starts with whitespace!")
	end
	
	local returnString, returnLastCharNum
	local addition = false -- tells if a - was added at the end of the word
	
	local rev = s:reverse()
	local revLen = rev:len()
	if rev:sub(1, 1) == " " then
		returnString = s
		
	else local whitespaceBeforeLastWordPos = rev:find("%s")
		
		local sWithoutLastWord = whitespaceBeforeLastWordPos and rev:sub(whitespaceBeforeLastWordPos, revLen):reverse() or ""
		
		local wordToCut = rev:sub(1, whitespaceBeforeLastWordPos and whitespaceBeforeLastWordPos-1 or revLen)
		local wordToCutLen = wordToCut:len()
		for i = 1, wordToCutLen do
			if table.find(consonants, wordToCut:sub(i, i):lower()) then
				local nextChar, nextNextChar = wordToCut:sub(i+1, i+1):lower(), wordToCut:sub(i+2, i+2):lower()
				local nextCharConsonant = table.find(consonants, nextChar) ~= nil
				
				if i == wordToCutLen or (i+1 == wordToCutLen and nextCharConsonant) then
					returnString = sWithoutLastWord
					break
				end
				
				if ((nextCharConsonant and table.find(vowels, nextNextChar)) or table.find(vowels, nextChar)) and table.find(vowels, wordToCut:sub(i-1, i-1)) then
					returnString = sWithoutLastWord..wordToCut:sub(i+1, wordToCutLen):reverse().."-"
					addition = true
					break
				end
			end
		end
		
		if not returnString then
			returnString = sWithoutLastWord
		end
		
		if returnString == "" then
			returnString = s
		end
	end
	
	returnLastCharNum = lastCharNum-s:len()+returnString:len()
	if addition then
		returnLastCharNum -= 1 -- so that adding the - won't affect the behavior of the main function
	end
	return returnString, returnLastCharNum
end

-- parameters: the whole text, fontsize as a number, font as an enum, labelsize in pixels as a vector2 (TextLabel.AbsoluteSize)
-- the Y size isn't actually even used, so this function could be changed so that it just takes the x value as a number.
local function getLineStrings(wholeString, fontSize, font, labelSize)
	-- the text of each line is stored here. to get the number of lines you can do #lines
	local lines = {}
	
	local mainLoopRepeats = 0
	
	-- length of the whole string in pixels
	local wholeStringXLength = TextService:GetTextSize(wholeString, fontSize, font, frameSize).X
	
	-- number of lines the whole string will probably take
	local numberOfLines = wholeStringXLength/labelSize.X
	
	-- number of characters in the whole string
	local stringLen = wholeString:len()
	
	-- how many characters on the average there will be per line
	local charsPerLine = math.ceil(stringLen/numberOfLines)
	
	
	local latestLineLastCharNum = 0
	
	while latestLineLastCharNum < stringLen do
		if mainLoopRepeats == 25 then
			break
		end
		
		local currentFirstCharNum = latestLineLastCharNum+1
		local lastCharNum = math.clamp(latestLineLastCharNum+charsPerLine, 1, stringLen)
		
		local s = wholeString:sub(currentFirstCharNum, lastCharNum)
		local sSizeX = TextService:GetTextSize(s, fontSize, font, frameSize).X
		
		if sSizeX > labelSize.X then
			while TextService:GetTextSize(s, fontSize, font, frameSize).X > labelSize.X do
				lastCharNum -= 1
				s = s:sub(1, s:len()-1)
			end
		elseif lastCharNum < stringLen then
			s = wholeString:sub(currentFirstCharNum, lastCharNum+1)
			while TextService:GetTextSize(s, fontSize, font, frameSize).X < labelSize.X do
				lastCharNum += 1
				s = wholeString:sub(currentFirstCharNum, lastCharNum+1)
				if lastCharNum == stringLen then
					s = s.."_" -- a placeholder so that nothing important will be removed after this loop
					break
				end
			end
			s = s:sub(1, s:len()-1)
		end
		
		if lastCharNum < stringLen then
			s, lastCharNum = cutLine(s, lastCharNum)
		end
		
		local sRev, sLen = s:reverse(), s:len()
		local whiteSpaceOnNextLineStart = wholeString:sub(lastCharNum+1, lastCharNum+1):match("%s")
		if whiteSpaceOnNextLineStart then
			local nonWhiteSpaceStart = sRev:find("%S")
			-- if there is something else than whitespace in somewhere in the current line and the whitespace in the beginning of the next line is't tab
			if nonWhiteSpaceStart and whiteSpaceOnNextLineStart ~= "\t" then
				-- preventing the next line from starting with whitespace
				s, lastCharNum = cutLine(s:sub(1, sLen-nonWhiteSpaceStart+1), lastCharNum-nonWhiteSpaceStart+1)
			end
		end
		
		latestLineLastCharNum = lastCharNum
		mainLoopRepeats += 1
		lines[mainLoopRepeats] = s
	end
	return lines
end
2 Likes