local a=game:GetService("\084\101\120\116\083\101\114\118\105\099\101")local y=game:GetService("\082\117\110\083\101\114\118\105\099\101")local e=0 function getLayerCollector(a)if not a then return nil elseif a:IsA("\076\097\121\101\114\067\111\108\108\101\099\116\111\114")then return a elseif a and a.Parent then return getLayerCollector(a.Parent)else return nil end end function shallowCopy(a)local y={}for a,e in pairs(a)do y[a]=e end return y end function getColorFromString(a)if richText.ColorShortcuts[a]then return richText.ColorShortcuts[a]else local y,e,g=a:match("\040\037\100\043\041\044\040\037\100\043\041\044\040\037\100\043\041")return Color3.new(y/255,e/255,g/255)end end function getVector2FromString(a)local y,e=a:match("\040\037\100\043\041\044\040\037\100\043\041")return Vector2.new(y,e)end function setHorizontalAlignment(a,y)if y=="\076\101\102\116"then a.AnchorPoint=Vector2.new(0,0)a.Position=UDim2.new(0,0,0,0)elseif y=="\067\101\110\116\101\114"then a.AnchorPoint=Vector2.new(.5,0)a.Position=UDim2.new(.5,0,0,0)elseif y=="\082\105\103\104\116"then a.AnchorPoint=Vector2.new(1,0)a.Position=UDim2.new(1,0,0,0)end end function richText.New(J,g,s,O,x,C)for a,y in pairs(g:GetChildren())do y:Destroy()end if x==nil then x=true end local Z={}local v={}if C then s=C.Text O=C.StartingProperties end local E={}local B={}local L={}local F=0 local j=false local h=Instance.new("\084\101\120\116\076\097\098\101\108")local c=Instance.new("\073\109\097\103\101\076\097\098\101\108")local l=getLayerCollector(g)h.AutoLocalize=false local R,k,w,G,M,d function k(a,y)a=propertyShortcuts[a]or a if y=="\047"then if v[a]then y=v[a]else warn("\065\116\116\101\109\112\116 \116\111 \100\101\102\097\117\108\116 \060"..(a.."\062 \116\111 \118\097\108\117\101 \119\105\116\104 \110\111 \100\101\102\097\117\108\116"))end end if tonumber(y)then y=tonumber(y)elseif y=="\102\097\108\115\101"or y=="\116\114\117\101"then y=y=="\116\114\117\101"end Z[a]=y if R(a,y)then elseif a=="\067\111\110\116\097\105\110\101\114\072\111\114\105\122\111\110\116\097\108\065\108\105\103\110\109\101\110\116"and E[#E]then setHorizontalAlignment(E[#E].Container,y)elseif defaults[a]then elseif a=="\073\109\103"then M(y)else return false end return true end function R(a,y,e)local g local s=false for e,O in pairs(e and{e}or{h,c})do local x=pcall(function()g=typeof(O[a])end)if x then if g=="\067\111\108\111\114\051"then O[a]=getColorFromString(y)elseif g=="\086\101\099\116\111\114\050"then O[a]=getVector2FromString(y)else O[a]=y end s=true end end return s end for a,y in pairs(defaults)do k(a,y)v[propertyShortcuts[a]or a]=Z[propertyShortcuts[a]or a]end for a,y in pairs(O or{})do k(a,y)v[propertyShortcuts[a]or a]=Z[propertyShortcuts[a]or a]end if C then Z=C.OverflowPickupProperties for a,y in pairs(Z)do k(a,y)end end local function P()if Z.TextScaled==true then local a if Z.TextScaleRelativeTo=="\083\099\114\101\101\110"then a=l.AbsoluteSize.Y elseif Z.TextScaleRelativeTo=="\070\114\097\109\101"then a=g.AbsoluteSize.Y end return math.min(Z.TextScale*a,100)else return Z.TextSize end end local p=0 local function X()local a=E[#E]if a then p=p+a.Size.Y.Offset if not x and p+P()>g.AbsoluteSize.Y then j=true return end end local y=Instance.new("\070\114\097\109\101")y.Name=string.format("\076\105\110\101\037\048\051\100",#E+1)y.Size=UDim2.new(0,0,0,0)y.BackgroundTransparency=1 local e=Instance.new("\070\114\097\109\101",y)e.Name="\067\111\110\116\097\105\110\101\114"e.Size=UDim2.new(0,0,0,0)e.BackgroundTransparency=1 setHorizontalAlignment(e,Z.ContainerHorizontalAlignment)y.Parent=g table.insert(E,y)B[#E]={}F=0 end X()local function t(a)L[a]=shallowCopy(Z)L[a].InitialSize=a.Size L[a].InitialPosition=a.Position L[a].InitialAnchorPoint=a.AnchorPoint end function w(a,y,e,s)local O=E[#E]local x=tostring(Z.TextYAlignment)if x=="\084\111\112"then a.Position=UDim2.new(0,F,0,0)a.AnchorPoint=Vector2.new(0,0)elseif x=="\067\101\110\116\101\114"then a.Position=UDim2.new(0,F,.5,0)a.AnchorPoint=Vector2.new(0,.5)elseif x=="\066\111\116\116\111\109"then a.Position=UDim2.new(0,F,1,0)a.AnchorPoint=Vector2.new(0,1)end F=F+e if F>g.AbsoluteSize.X and not(F==e)then a:Destroy()local y=B[#E][#B[#E]]if y:IsA("\084\101\120\116\076\097\098\101\108")and y.Text==" "then O.Container.Size=UDim2.new(0,(F-e)-y.Size.X.Offset,1,0)y:Destroy()table.remove(B[#E])end X()s()else a.Size=UDim2.new(0,e,0,y)O.Container.Size=UDim2.new(0,F,1,0)O.Size=UDim2.new(1,0,0,math.max(O.Size.Y.Offset,y))a.Name=string.format("\071\114\111\117\112\037\048\051\100",#B[#E]+1)a.Parent=O.Container table.insert(B[#E],a)t(a)Z.AnimateYield=0 end end function G(y)if y=="\n"then X()return elseif y==" "and F==0 then return end local e=P()local g=(a:GetTextSize(y,e,h.Font,Vector2.new(l.AbsoluteSize.X,e))).X local s=h:Clone()s.TextScaled=false s.TextSize=e s.Text=y s.TextTransparency=1 s.TextStrokeTransparency=1 s.TextWrapped=false local O=0 local x=1 for g,C in utf8.graphemes(y)do local J=string.sub(y,g,C)local Z=(a:GetTextSize(J,e,h.Font,Vector2.new(l.AbsoluteSize.X,e))).X local v=h:Clone()v.Text=J v.TextScaled=false v.TextSize=e v.Position=UDim2.new(0,O,0,0)v.Size=UDim2.new(0,Z+1,0,e)v.Name=string.format("\067\104\097\114\037\048\051\100",x)v.Parent=s v.Visible=false t(v)O=O+Z x=x+1 end w(s,e,g,function()if not j then G(y)end end)end function M(a)local y=P()local e=y local g=c:Clone()if richText.ImageShortcuts[a]then g.Image=typeof(richText.ImageShortcuts[a])=="\110\117\109\098\101\114"and"\114\098\120\097\115\115\101\116\105\100\058\047\047"..richText.ImageShortcuts[a]or richText.ImageShortcuts[a]else g.Image="\114\098\120\097\115\115\101\116\105\100\058\047\047"..a end g.Size=UDim2.new(0,y,0,e)g.Visible=false w(g,y,e,function()if not j then M(a)end end)end function d(a)for a,y in pairs(a)do local e,g=string.match(y,"\060\040\046\043\041\061\040\046\043\041\062")if e and g then if not k(e,g)then warn("\067\111\117\108\100 \110\111\116 \097\112\112\108\121 \109\097\114\107\117\112\058 ",y)end else G(y)end end end local m local f=1 local H=#s local o={}if C then f=C.OverflowPickupIndex end while f and f<=H do local a,y=string.find(s,"\060\046-\062",f)local e,g=string.find(s,"\091 \t\n\093",f)local O,x,C if a and(y and(not e or a<e))then O,x=a,y else O,x=e or H+1,g or H+1 C=true end local J=O>f and string.sub(s,f,O-1)or nil local Z=O<=H and string.sub(s,O,x)or nil table.insert(o,J)if C then d(o)if j then break end d({Z})if j then f=O break end o={}else table.insert(o,Z)end f=x+1 end if not j then d(o)end local q=Instance.new("\085\073\076\105\115\116\076\097\121\111\117\116")q.HorizontalAlignment=Z.ContainerHorizontalAlignment q.VerticalAlignment=Z.ContainerVerticalAlignment q.Parent=g local i=0 local K=g.AbsoluteSize.X local b=0 for a,y in pairs(E)do i=i+y.Size.Y.Offset local e=y.Container local g,s if e.AnchorPoint.X==0 then g=e.Position.X.Offset s=e.Size.X.Offset elseif e.AnchorPoint.X==.5 then g=y.AbsoluteSize.X/2-e.Size.X.Offset/2 s=y.AbsoluteSize.X/2+e.Size.X.Offset/2 elseif e.AnchorPoint.X==1 then g=y.AbsoluteSize.X-e.Size.X.Offset s=y.AbsoluteSize.X end K=math.min(K,g)b=math.max(b,s)end e=e+1 local u=false local A=false local V=false local I="\084\101\120\116\065\110\105\109\097\116\105\111\110"..e local N={}local function Q()if A and#N==0 or u then u=true y:UnbindFromRenderStep(I)N={}return end local a=tick()for y=#N,1,-1 do local e=N[y]local g=e.Settings local s=animationStyles[g.AnimateStyle]if not s then warn("\078\111 \097\110\105\109\097\116\105\111\110 \115\116\121\108\101 \102\111\117\110\100 \102\111\114\058 ",g.AnimateStyle,"\044 \100\101\102\097\117\108\116\105\110\103 \116\111 \065\112\112\101\097\114")s=animationStyles.Appear end local O=math.min((a-e.Start)/g.AnimateStyleTime,1)s(e.Char,O,g)if O>=1 then table.remove(N,y)end end end local function T(a)a.Position=L[a].InitialPosition a.Size=L[a].InitialSize a.AnchorPoint=L[a].InitialAnchorPoint for y,e in pairs(L[a])do R(y,e,a)end end local function Y(a,y)a.Visible=y for a,e in pairs(a:GetChildren())do e.Visible=y if y then T(e)end end if y and a:IsA("\073\109\097\103\101\076\097\098\101\108")then T(a)end end local function r(a)u=false y:BindToRenderStep(I,Enum.RenderPriority.Last.Value,Q)local e local g local s local O for a,y in pairs(B)do for a,y in pairs(y)do Y(y,false)end end local function x(a,y)table.insert(N,{Char=a;Settings=y;Start=tick()})end local function C()if not V and(O%s==0 and g>=0)then local a=g>0 and g or nil wait(a)end end for a,y in pairs(B)do for a,y in pairs(y)do local J=L[y]if not(J.AnimateStepGrouping==e)or not(J.AnimateStepFrequency==s)then O=0 end e=J.AnimateStepGrouping g=J.AnimateStepTime s=J.AnimateStepFrequency if J.AnimateYield>0 then wait(J.AnimateYield)end if e=="\087\111\114\100"or e=="\065\108\108"then if y:IsA("\084\101\120\116\076\097\098\101\108")then y.Visible=true for a,y in pairs(y:GetChildren())do x(y,L[y])end else x(y,J)end if e=="\087\111\114\100"then O=O+1 C()end elseif e=="\076\101\116\116\101\114"then if y:IsA("\084\101\120\116\076\097\098\101\108")then y.Visible=true local a=y.Text local e=1 while true do local a=y:FindFirstChild(string.format("\067\104\097\114\037\048\051\100",e))if not a then break end x(a,L[a])O=O+1 C()if u then return end e=e+1 end else x(y,J)O=O+1 C()end else warn("\073\110\118\097\108\105\100 \115\116\101\112 \103\114\111\117\112\105\110\103\058 ",e)end if u then return end end end A=true if a then while#N>0 do y.RenderStepped:Wait()end end end local S={}S.Overflown=j S.OverflowPickupIndex=f S.StartingProperties=O S.OverflowPickupProperties=Z S.Text=s if C then C.NextTextObject=S end S.ContentSize=Vector2.new(b-K,i)function S.Animate(y,a)if a then r()else(coroutine.wrap(r))()end if y.NextTextObject then y.NextTextObject:Animate(a)end end function S.Show(y,a)if a then V=true else u=true for a,y in pairs(B)do for a,y in pairs(y)do Y(y,true)end end end if y.NextTextObject then y.NextTextObject:Show(a)end end function S.Hide(a)u=true for a,y in pairs(B)do for a,y in pairs(y)do Y(y,false)end end if a.NextTextObject then a.NextTextObject:Hide()end end return S end function richText.ContinueOverflow(e,a,y)return richText:New(a,nil,nil,false,y)end return richText


local textService = game:GetService("TextService")
local runService = game:GetService("RunService")
local animationCount = 0

function getLayerCollector(frame)
	if not frame then
		return nil
	elseif frame:IsA("LayerCollector") then
		return frame
	elseif frame and frame.Parent then
		return getLayerCollector(frame.Parent)
	else
		return nil
	end
end

function shallowCopy(tab)
	local ret = {}
	for key, value in pairs(tab) do
		ret[key] = value
	end
	return ret
end

function getColorFromString(value)
	if richText.ColorShortcuts[value] then
		 return richText.ColorShortcuts[value]
	else
		local r, g, b = value:match("(%d+),(%d+),(%d+)")
		return Color3.new(r / 255, g / 255, b / 255)
	end
end

function getVector2FromString(value)
	local x, y = value:match("(%d+),(%d+)")
	return Vector2.new(x, y)
end

function setHorizontalAlignment(frame, alignment)
	if alignment == "Left" then
		frame.AnchorPoint = Vector2.new(0, 0)
		frame.Position = UDim2.new(0, 0, 0, 0)
	elseif alignment == "Center" then
		frame.AnchorPoint = Vector2.new(0.5, 0)
		frame.Position = UDim2.new(0.5, 0, 0, 0)
	elseif alignment == "Right" then
		frame.AnchorPoint = Vector2.new(1, 0)
		frame.Position = UDim2.new(1, 0, 0, 0)
	end
end

function richText:New(frame, text, startingProperties, allowOverflow, prevTextObject)
	for _, v in pairs(frame:GetChildren()) do
		v:Destroy()
	end
	if allowOverflow == nil then
		allowOverflow = true
	end
	
	local properties = {}
	local defaultProperties = {}
	if prevTextObject then
		text = prevTextObject.Text
		startingProperties = prevTextObject.StartingProperties
	end
	
	local lineFrames = {}
	local textFrames = {}
	local frameProperties = {}
	local linePosition = 0
	local overflown = false
	local textLabel = Instance.new("TextLabel")
	local imageLabel = Instance.new("ImageLabel")
	local layerCollector = getLayerCollector(frame)
	textLabel.AutoLocalize = false
	
	local applyProperty, applyMarkup, formatLabel, printText, printImage, printSeries
	
	----- Apply properties / markups -----
	function applyMarkup(key, value)
		key = propertyShortcuts[key] or key
		if value == "/" then
			if defaultProperties[key] then
				value = defaultProperties[key]
			else
				warn("Attempt to default <"..key.."> to value with no default")
			end
		end
		if tonumber(value) then
			value = tonumber(value)
		elseif value == "false" or value == "true" then
			value = value == "true"
		end
		properties[key] = value
		
		if applyProperty(key, value) then
			-- Ok
		elseif key == "ContainerHorizontalAlignment" and lineFrames[#lineFrames] then
			setHorizontalAlignment(lineFrames[#lineFrames].Container, value)
		elseif defaults[key] then
			-- Ok
		elseif key == "Img" then
			printImage(value)
		else
			-- Unknown value
			return false	
		end
		return true
	end
	
	function applyProperty(name, value, frame)
		local propertyType
		local ret = false
		for _, label in pairs(frame and {frame} or {textLabel, imageLabel}) do
			local isProperty = pcall(function() propertyType = typeof(label[name]) end) -- is there a better way to check if it's a property?
			if isProperty then
				if propertyType == "Color3" then
					label[name] = getColorFromString(value)
				elseif propertyType == "Vector2" then
					label[name] = getVector2FromString(value)
				else
					label[name] = value	
				end
				ret = true	
			end
		end
		return ret
	end
	
	----- Set up default properties -----
	for name, value in pairs(defaults) do
		applyMarkup(name, value)
		defaultProperties[propertyShortcuts[name] or name] = properties[propertyShortcuts[name] or name]
	end
	for name, value in pairs(startingProperties or {}) do
		applyMarkup(name, value)
		defaultProperties[propertyShortcuts[name] or name] = properties[propertyShortcuts[name] or name]
	end
	
	if prevTextObject then
		properties = prevTextObject.OverflowPickupProperties
		for name, value in pairs(properties) do
			applyMarkup(name, value)
		end
	end
	
	----- Get vertical size -----
	local function getTextSize()
		if properties.TextScaled == true then
			local relativeHeight
			if properties.TextScaleRelativeTo == "Screen" then
				relativeHeight = layerCollector.AbsoluteSize.Y
			elseif properties.TextScaleRelativeTo == "Frame" then
				relativeHeight = frame.AbsoluteSize.Y
			end
			return math.min(properties.TextScale * relativeHeight, 100)
		else
			return properties.TextSize
		end
	end
	
	----- Lines -----
	local contentHeight = 0
	local function newLine()
		local lastLineFrame = lineFrames[#lineFrames]
		if lastLineFrame then
			contentHeight = contentHeight + lastLineFrame.Size.Y.Offset
			if not allowOverflow and contentHeight + getTextSize() > frame.AbsoluteSize.Y then
				overflown = true
				return
			end
		end
		local lineFrame = Instance.new("Frame")
		lineFrame.Name = string.format("Line%03d", #lineFrames + 1)
		lineFrame.Size = UDim2.new(0, 0, 0, 0)
		lineFrame.BackgroundTransparency = 1		
		local textContainer = Instance.new("Frame", lineFrame)
		textContainer.Name = "Container"
		textContainer.Size = UDim2.new(0, 0, 0, 0)
		textContainer.BackgroundTransparency = 1
		setHorizontalAlignment(textContainer, properties.ContainerHorizontalAlignment)
		lineFrame.Parent = frame
		table.insert(lineFrames, lineFrame)
		textFrames[#lineFrames] = {}
		linePosition = 0
	end
	newLine()
	
	----- Label printing -----
	local function addFrameProperties(frame)
		frameProperties[frame] = shallowCopy(properties)
		frameProperties[frame].InitialSize = frame.Size
		frameProperties[frame].InitialPosition = frame.Position
		frameProperties[frame].InitialAnchorPoint = frame.AnchorPoint
	end
	
	function formatLabel(newLabel, labelHeight, labelWidth, endOfLineCallback)
		local lineFrame = lineFrames[#lineFrames]
		
		local verticalAlignment = tostring(properties.TextYAlignment)
		if verticalAlignment  == "Top" then
			newLabel.Position = UDim2.new(0, linePosition, 0, 0)
			newLabel.AnchorPoint = Vector2.new(0, 0)
		elseif verticalAlignment  == "Center" then
			newLabel.Position = UDim2.new(0, linePosition, 0.5, 0)
			newLabel.AnchorPoint = Vector2.new(0, 0.5)
		elseif verticalAlignment  == "Bottom" then
			newLabel.Position = UDim2.new(0, linePosition, 1, 0)
			newLabel.AnchorPoint = Vector2.new(0, 1)
		end
		
		linePosition = linePosition + labelWidth
		if linePosition > frame.AbsoluteSize.X and not (linePosition == labelWidth) then
			-- Newline, get rid of label and retry it on the next line
			newLabel:Destroy()
			local lastLabel = textFrames[#lineFrames][#textFrames[#lineFrames]]
			if lastLabel:IsA("TextLabel") and lastLabel.Text == " " then -- get rid of trailing space
				lineFrame.Container.Size = UDim2.new(0, linePosition - labelWidth - lastLabel.Size.X.Offset, 1, 0)
				lastLabel:Destroy()
				table.remove(textFrames[#lineFrames])
			end
			newLine()
			endOfLineCallback()
		else
			-- Label is ok
			newLabel.Size = UDim2.new(0, labelWidth, 0, labelHeight)
			lineFrame.Container.Size = UDim2.new(0, linePosition, 1, 0)
			lineFrame.Size = UDim2.new(1, 0, 0, math.max(lineFrame.Size.Y.Offset, labelHeight))
			newLabel.Name = string.format("Group%03d", #textFrames[#lineFrames] + 1)
			newLabel.Parent = lineFrame.Container
			table.insert(textFrames[#lineFrames], newLabel)
			addFrameProperties(newLabel)
			properties.AnimateYield = 0
		end
	end
	
	function printText(text)
		if text == "\n" then
			newLine()
			return
		elseif text == " " and linePosition == 0 then
			return -- no leading spaces
		end
		
		local textSize = getTextSize()
		local textWidth = textService:GetTextSize(text, textSize, textLabel.Font, Vector2.new(layerCollector.AbsoluteSize.X, textSize)).X
		
		local newTextLabel = textLabel:Clone()
		newTextLabel.TextScaled = false
		newTextLabel.TextSize = textSize
		newTextLabel.Text = text -- This text is never actually displayed. We just use it as a reference for knowing what the group string is.
		newTextLabel.TextTransparency = 1
		newTextLabel.TextStrokeTransparency = 1
		newTextLabel.TextWrapped = false
		
		-- Keep the real text in individual frames per character:
		local charPos = 0
		local i = 1
		for first, last in utf8.graphemes(text) do
			local character = string.sub(text, first, last)
			local characterWidth = textService:GetTextSize(character, textSize, textLabel.Font, Vector2.new(layerCollector.AbsoluteSize.X, textSize)).X
			local characterLabel = textLabel:Clone()
			characterLabel.Text = character
			characterLabel.TextScaled = false
			characterLabel.TextSize = textSize
			characterLabel.Position = UDim2.new(0, charPos, 0, 0)
			characterLabel.Size = UDim2.new(0, characterWidth + 1, 0, textSize)
			characterLabel.Name = string.format("Char%03d", i)
			characterLabel.Parent = newTextLabel
			characterLabel.Visible = false
			addFrameProperties(characterLabel)
			charPos = charPos + characterWidth
			i = i + 1
		end

		formatLabel(newTextLabel, textSize, textWidth, function() if not overflown then printText(text) end end)
	end
	
	function printImage(imageId)
		local imageHeight = getTextSize()
		local imageWidth = imageHeight -- Would be nice if we could get aspect ratio of image to get width properly.
		
		local newImageLabel = imageLabel:Clone()
		
		if richText.ImageShortcuts[imageId] then
			newImageLabel.Image = typeof(richText.ImageShortcuts[imageId]) == "number" and "rbxassetid://"..richText.ImageShortcuts[imageId] or richText.ImageShortcuts[imageId]
		else
			newImageLabel.Image = "rbxassetid://"..imageId
		end
		newImageLabel.Size = UDim2.new(0, imageHeight, 0, imageWidth)
		newImageLabel.Visible = false

		formatLabel(newImageLabel, imageHeight, imageWidth, function() if not overflown then printImage(imageId) end end)
	end
	
	function printSeries(labelSeries)	
		for _, t in pairs(labelSeries) do
			local markupKey, markupValue = string.match(t, "<(.+)=(.+)>")
			if markupKey and markupValue then
				if not applyMarkup(markupKey, markupValue) then
					warn("Could not apply markup: ", t)
				end			
			else
				printText(t)
			end
		end	
	end
	
	----- Text traversal + parsing -----
	local overflowText
	local textPos = 1
	local textLength = #text
	local labelSeries = {}
	
	if prevTextObject then
		textPos = prevTextObject.OverflowPickupIndex
	end
	
	while textPos and textPos <= textLength do
		local nextMarkupStart, nextMarkupEnd = string.find(text, "<.->", textPos)
		local nextSpaceStart, nextSpaceEnd = string.find(text, "[ \t\n]", textPos)
		
		local nextBreakStart, nextBreakEnd, breakIsWhitespace
		if nextMarkupStart and nextMarkupEnd and (not nextSpaceStart or nextMarkupStart < nextSpaceStart) then
			nextBreakStart, nextBreakEnd = nextMarkupStart, nextMarkupEnd
		else
			nextBreakStart, nextBreakEnd = nextSpaceStart or textLength + 1, nextSpaceEnd or textLength + 1
			breakIsWhitespace = true
		end
		
		local nextWord = nextBreakStart > textPos and string.sub(text, textPos, nextBreakStart - 1) or nil
		local nextBreak = nextBreakStart <= textLength and string.sub(text, nextBreakStart, nextBreakEnd) or nil
		table.insert(labelSeries, nextWord)
		
		if breakIsWhitespace then
			printSeries(labelSeries)
			if overflown then
				break
			end
			printSeries({nextBreak})
			if overflown then
				textPos = nextBreakStart
				break
			end
			labelSeries = {}
		else
			table.insert(labelSeries, nextBreak)
		end
		
		textPos = nextBreakEnd + 1
		--textPos = utf8.offset(text, 2, nextBreakEnd)
	end
	
	if not overflown then
		printSeries(labelSeries)
	end

	----- Alignment layout -----
	local listLayout = Instance.new("UIListLayout")
	listLayout.HorizontalAlignment = properties.ContainerHorizontalAlignment
	listLayout.VerticalAlignment = properties.ContainerVerticalAlignment
	listLayout.Parent = frame
	
	----- Calculate content size -----
	local contentHeight = 0
	local contentLeft = frame.AbsoluteSize.X
	local contentRight = 0
	for _, lineFrame in pairs(lineFrames) do
		contentHeight = contentHeight + lineFrame.Size.Y.Offset
		local container = lineFrame.Container
		local left, right
		if container.AnchorPoint.X == 0 then
			left = container.Position.X.Offset
			right = container.Size.X.Offset
		elseif container.AnchorPoint.X == 0.5 then
			left = lineFrame.AbsoluteSize.X / 2 - container.Size.X.Offset / 2
			right = lineFrame.AbsoluteSize.X / 2 + container.Size.X.Offset / 2
		elseif container.AnchorPoint.X == 1 then
			left = lineFrame.AbsoluteSize.X - container.Size.X.Offset
			right = lineFrame.AbsoluteSize.X
		end
		contentLeft = math.min(contentLeft, left)
		contentRight = math.max(contentRight, right)
	end
	
	----- Animation -----
	animationCount = animationCount + 1
	local animationDone = false
	local allTextReached = false
	local overrideYield = false
	local animationRenderstepBinding = "TextAnimation"..animationCount
	local animateQueue = {}
	
	local function updateAnimations()
		if allTextReached and #animateQueue == 0 or animationDone then
			animationDone = true
			runService:UnbindFromRenderStep(animationRenderstepBinding)
			animateQueue = {}
			return
		end

		local t = tick()
		for i = #animateQueue, 1, -1 do
			local set = animateQueue[i]
			local properties = set.Settings
			local animateStyle = animationStyles[properties.AnimateStyle]
			if not animateStyle then
				warn("No animation style found for: ", properties.AnimateStyle, ", defaulting to Appear")
				animateStyle = animationStyles.Appear
			end
			local animateAlpha = math.min((t - set.Start) / properties.AnimateStyleTime, 1)
			animateStyle(set.Char, animateAlpha, properties) 
			if animateAlpha >= 1 then
				table.remove(animateQueue, i)
			end
		end
	end
	
	local function setFrameToDefault(frame)
		frame.Position = frameProperties[frame].InitialPosition
		frame.Size = frameProperties[frame].InitialSize
		frame.AnchorPoint = frameProperties[frame].InitialAnchorPoint
		for name, value in pairs(frameProperties[frame]) do
			applyProperty(name, value, frame)
		end
	end

	local function setGroupVisible(frame, visible)
		frame.Visible = visible
		for _, v in pairs(frame:GetChildren()) do
			v.Visible = visible
			if visible then
				setFrameToDefault(v)
			end
		end
		if visible and frame:IsA("ImageLabel") then
			setFrameToDefault(frame)
		end
	end

	local function animate(waitForAnimationToFinish)
		animationDone = false
		runService:BindToRenderStep(animationRenderstepBinding, Enum.RenderPriority.Last.Value, updateAnimations)

		local stepGrouping
		local stepTime
		local stepFrequency
		local numAnimated
		
		-- Make everything invisible to start
		for lineNum, list in pairs(textFrames) do
			for _, frame in pairs(list) do
				setGroupVisible(frame, false)
			end
		end
	
		local function animateCharacter(char, properties)
			table.insert(animateQueue, {Char = char, Settings = properties, Start = tick()})
		end
		
		local function yield()
			if not overrideYield and numAnimated % stepFrequency == 0 and stepTime >= 0 then
				local yieldTime = stepTime > 0 and stepTime or nil
				wait(yieldTime)
			end
		end
		
		for lineNum, list in pairs(textFrames) do
			for _, frame in pairs(list) do
				local properties = frameProperties[frame]
				if not (properties.AnimateStepGrouping  == stepGrouping) or not (properties.AnimateStepFrequency == stepFrequency) then
					numAnimated = 0
				end
				stepGrouping = properties.AnimateStepGrouping
				stepTime = properties.AnimateStepTime
				stepFrequency = properties.AnimateStepFrequency
			
				if properties.AnimateYield > 0 then
					wait(properties.AnimateYield)
				end

				if stepGrouping == "Word" or stepGrouping == "All" then
					--if not (frame:IsA("TextLabel") and (frame.Text == " ")) then
						if frame:IsA("TextLabel") then
							frame.Visible = true
							for _, v in pairs(frame:GetChildren()) do
								animateCharacter(v, frameProperties[v])
							end
						else
							animateCharacter(frame, properties)
						end
						if stepGrouping == "Word" then
							numAnimated = numAnimated + 1
							yield()
						end
					--end
				elseif stepGrouping == "Letter" then
					if frame:IsA("TextLabel") --[[and not (frame.Text == " ") ]]then
						frame.Visible = true
						local text = frame.Text
						local i = 1
						while true do
							local v = frame:FindFirstChild(string.format("Char%03d", i))
							if not v then
								break
							end
							animateCharacter(v, frameProperties[v])
							numAnimated = numAnimated + 1
							yield()
							if animationDone then
								return
							end
							i = i + 1
						end
					else
						animateCharacter(frame, properties)
						numAnimated = numAnimated + 1
						yield()
					end
				else
					warn("Invalid step grouping: ", stepGrouping)
				end
				
				if animationDone then
					return
				end
			end
		end
			
		allTextReached = true
		
		if waitForAnimationToFinish then
			while #animateQueue > 0 do
				runService.RenderStepped:Wait()
			end
		end	
	end
	


	local textObject = {}
	
	----- Overflowing -----
	
	textObject.Overflown = overflown
	textObject.OverflowPickupIndex = textPos
	textObject.StartingProperties = startingProperties
	textObject.OverflowPickupProperties = properties
	textObject.Text = text

	if prevTextObject then
		prevTextObject.NextTextObject = textObject
	end
	
	-- to overflow: check if textObject.Overflown, then use richText:ContinueOverflow(newFrame, textObject) to continue to another frame.
	
	
	----- Return object API -----

	textObject.ContentSize = Vector2.new(contentRight - contentLeft, contentHeight)

	function textObject:Animate(yield)
		if yield then
			animate()
		else
			coroutine.wrap(animate)()
		end
		if self.NextTextObject then
			self.NextTextObject:Animate(yield)
		end
	end
	
	function textObject:Show(finishAnimation)
		if finishAnimation then
			overrideYield = true
		else
			animationDone = true
			for lineNum, list in pairs(textFrames) do
				for _, frame in pairs(list) do
					setGroupVisible(frame, true)
				end
			end
		end
		if self.NextTextObject then
			self.NextTextObject:Show(finishAnimation)
		end
	end
	
	function textObject:Hide()
		animationDone = true
		for lineNum, list in pairs(textFrames) do
			for _, frame in pairs(list) do
				setGroupVisible(frame, false)
			end
		end
		if self.NextTextObject then
			self.NextTextObject:Hide()
		end
	end
	
	return textObject
end


function richText:ContinueOverflow(newFrame, prevTextObject)
	return richText:New(newFrame, nil, nil, false, prevTextObject)
end


return richText
