Can I have help with carefully cutting strings?

I have been working on a custom chat for the last week or so. I managed to get most of it done. However, I have it that is the player chats something that is long it will wrap into another TextLabel. There is a problem with this though.

If I use string.sub to cut the strings to put in the separate chat TextLabel it cuts the string off at inconvenient places. Like this

Hi there, the pro
blem with my ch
at is it is way har
der to read whe
n cut up like thi
s.

I would much rather have it like this:

Hi there, it
would be a
lot better with
my chat cut
up like this.

So what I need is a function that carefully cuts a string at space but is still short enough to fit in my text label. So I did some stretches and spent a few hours making the most complex masterpiece I ever made(because I am not a pro coder) that would return a number that would be the best place to cut that string:

local function carefulCutCor(msg,i,j,front,maxSize)–msg is the fuul msg that will be searched:
–i number of the beginin of the chuck that will be searched for space cut convinient:
–j number of the end of the chuck that will be searched for space cut convinient :
–front wether the space’s should be rounded in the start of the msg or the end :
–maxSize the maximium size of what the string will be if rounded to nearest space
if string.find(msg," ") ~= nil then – wether the msg has no space to round to
local openSpace – the varible of the position of the space

	if front then -- whether the space's should be rounded in the start of the msg or the end
		openSpace = i -- if its in the front then the number of the beginning of the chuck( the one that will be searched for space cut convinient) is the openSpace so far
		while string.sub(msg,openSpace,openSpace) ~= " " do -- while the openspace is not a space then keep seardhing up
			openSpace = openSpace + 1
		end
		if string.len(string.sub(msg,openSpace,j)) > maxSize then -- if what the string would be if rounded is more then the maxSize then abort funcion
			openSpace = j
		end
	else
		openSpace = j -- if its in the front then the number of the end of the chuck( the one that will be searched for space cut convenient) is the openSpace so far
		while string.sub(msg,openSpace,openSpace) ~= " " do -- while the openspace is not a space then keep searching down
			openSpace = openSpace - 1
		end
		if string.len(string.sub(msg,i,openSpace)) > maxSize then -- if what the string would be if rounded is more then the maxSize then abort funcion
			openSpace = j
		end
	end
	return openSpace
else
	return j -- if no spaces return the number of the end of the chuck that will be searched for space cut convenient
	end	
end

MessageLine1 = string.sub(msg,0,carefulCutCor(msg,0,60,false,60))
MessageLine2 = string.sub(msg,carefulCutCor(msg,60,string.len(msg),true,85),string.len(msg))

(Sorry if comments are bad grammar, I had written them for myself)

My problem is that this function sometimes will remove a word because of the lines are searching in different directions for the openSpace.

I could make them search in the same direction for the open space but then the string would pass the max size. Example:

“This is a string that I want to be cut into two readable lines right where said the letter w in two.”

If the both searched in the same directions the function would take this and make this:“This is a string that I want to be cut into” “readable lines right where I said the letter w in two.”

And if the search in different directions it would be: “This is a string that I want to be cut into” “two readable lines right where I said the letter w in two.”

And the latter looks good but the second string would surpass the maxSize because the LineTwoMessage TextLabel can only take something as big as “wo readable lines right where I said the letter w in two.”

Then I would have to abort the last letter(two) in the second line to go to the third and that’s where my brain crashes. I am just not smart enough.

So can someone please help me out with this?

I’d approach this as follows;

Start by taking each character and checking if it’s a space; (keep an index of where in the string the character is)

  • if it isn’t, add it to a temp variable (it slowly fills)
  • if it is, check if the length of the temp variable exceeds the max size of one line
    • if it does not exceed max length, store the current temp variable and its index as last saved snippet to use when it does exceed
    • if it exceeds max length, go back to the last saved snippet and substract that from the message (use the index you kept)

each time it exceeds the length, you take the snippet you last saved as ‘one line’, you remove it from the full message (use the last saved index to take the substring from there to the end), and start anew with that remaining message

I am sorry, I can’t quite understand you. Can you give me some coded examples?

Not quite checked, consider it pseudo-code

local buffer = ""

local last_save = {s = nil,i = nil}

local MAX_SIZE = 40 -- idk

for i = 1,string.len(somestring) do
	local char = string.sub(s,i,i)
	if char == ' ' then
		if string.len(buffer) > MAX_SIZE then
			return last_save
		else
			last_save.s = buffer
			last_save.i = i
		end
	end
	buffer = buffer .. char
end

Split message on spaces;

function splitMessage(message)
	local result = {}
	for s in message:gmatch("[^%s]+") do
		table.insert(result, s)
	end
	return result
end

Find out the max size of your label (how much letters can it hold)
Check length of each word, if it fits, render it.
If it doesn’t fit, add \n to the end and re-do the process.

(Typed this in rush, if you need help, feel free to reply)

Here is my attempt to implement your example. I think you might of misunderstood me. I need the strings to be nicely cut on both ends. The code outputs a error because msgThreeStart is nil.

local function carefulCuttor(somestring,maxSize)
	local buffer = ""
	local Save = {s = nil,i = nil}
	
	for i = 1,string.len(somestring) do
		local char = string.sub(somestring,i,i)
		if char == ' ' then
			if string.len(buffer) > maxSize then
				return Save.s,Save.i
			else
				Save.s = buffer
				Save.i = i
			end
		end
		buffer = buffer .. char
	end
end

local s = "cut me aoibsna doiu aosu nou nao uisno no anso oaiun onao in aonsa on oian onaso naos inaoi na on"

local msg1,msgTwoStart = carefulCuttor(s,42)
print(msgTwoStart)
local msg2,msgThreeStart = carefulCuttor(string.sub(s,msgTwoStart,77),42)
print(msgThreeStart)
local msg3,notNeeded = carefulCuttor(string.sub(s,msgThreeStart,string.len(s)),42)

print(s)
print(msg1,msg2,msg3)

function cut(bla)
	local buffer = ""

	local last_save = {s = nil,i = nil}

	local MAX_SIZE = 40 -- idk

	if string.len(bla) <= MAX_SIZE then
		return {s = bla,i = string.len(bla) + 1}
	end

	for i = 1,string.len(bla) do
		local char = string.sub(bla,i,i)
		if char == ' ' then
			if string.len(buffer) > MAX_SIZE then
				return last_save
			else
				last_save.s = buffer
				last_save.i = i
			end
		end
		buffer = buffer .. char
	end
end

local mystring = [[
So what I need is a function that carefully cuts a string at space but is still short enough to fit in my text label. So I did some stretches and spent a few hours making the most complex masterpiece I ever made(because I am not a pro coder) that would return a number that would be the best place to cut that string:
]]

local result = ""

while string.len(mystring) > 0 do
	local snippet = cut(mystring)
	mystring = string.sub(mystring,snippet.i)
	result = result .. snippet.s .. "\n"
end

print(result)

results in

So what I need is a function that
carefully cuts a string at space but is
still short enough to fit in my text
label. So I did some stretches and
spent a few hours making the most
complex masterpiece I ever made(because
I am not a pro coder) that would return
a number that would be the best place
to cut that string:

Each return of snippet.s within that loop is one line of the maximum defined char length

3 Likes

Let Roblox do the hard work for you! I think your best option here is to use a combination of:

  • The TextLabel.TextWrapped property (to make text overflow onto the next line on a TextLabel where needed),
  • The TextService.GetTextSize function (to calculate the needed height of a TextLabel to fit the wrapped text),
  • A UIListLayout or similar (to ensure that contents of your chat don’t overlap each other).

The idea here is to resize each TextLabel whenever the size of their parent container (e.g. a ScrollingFrame) changes such that each TextLabel is large enough (on the Y axis) to fit all of the text content. This is demonstrated below:

Here is some sample code:

local Frame = script.Parent:WaitForChild("Frame") -- container frame
local Service = game:GetService("TextService") -- textservice

Frame:GetPropertyChangedSignal("AbsoluteSize"):Connect(function() -- when container frame size changes
	for _, Label in next, Frame:GetChildren() do -- for each child
		if Label:IsA("TextLabel") then -- if it's a textlabel
			local Size = Service:GetTextSize( -- get the size of this text, given the width of the label
				Label.Text, 
				Label.TextSize, 
				Label.Font, 
				Vector2.new(Label.AbsoluteSize.x, 1e5) -- no limit on the y (we want to know how far to extend down)
			)
			Label.Size = UDim2.new(1, 0, 0, Size.y) -- set the size
			-- the UIListLayout will push other elements down to fit it
		end
	end
end)

Here is a picture of the explorer hierarchy used in the example:

image

1 Like

I was thinking of that but the chat TextLabels are not the same length to make room for the player’s name:
Annotation%202019-03-03%20193230

I think it’s gonna work. I needed to add some stuff and fix some bugs but I really don’t know what I would do without your help. Thanks man.

1 Like