how would i split a string that uses bbcode and render it as the right bbcode?

i want to properly render strings with bbcode and some animations. i.e. this string should be rendered as a red Hello World! There’s no pressing need for this, just wanted to try it this way. if i really cant figure it out I’ll go back to the function that takes alternating strings and colors, but imo bbcode would be a lot easier to read and use since im already used to it, plus it would be a lot more flexible than taking and checking the type of multiple function arguments

examplestring=“[color=#ff0032]Hello World![/color]”

Actual code below. Current way to displaying different colors is to set the colors table, similar to setting mesh.colors

-- texttest

-- Use this function to perform your initial setup
function setup()
    test0 = coloredtext(teststring,100,HEIGHT/2,10,10,WIDTH/2)
    test2=coloredtext('a short string', 300, 200, 10,16,200)
    test2.nextshake=0.0 --causes it to bug out
    for i in ipairs(test2.colors) do
        test2.colors[i]=color(10*i,255-10*i,0)
    end
end

coloredtext = class()
function coloredtext:init(s,x,y,w,h,wrapwidth)
    self.colors = {}
    self.positions = {}
    self.strings = {}
    self.timer = 0
    self.nextshake = 1
    self.shakedirection = 1
    self.shakeindex = 1 --only used in coloredtext:update2()
    local i = 1
    local count = 0
    local wrapx = x + wrapwidth
    local newx, newy = x, y
    for c in s:gmatch"." do
        self.strings[i] = c
        self.colors[i] = color(255)
        newx = x + w * count
        newy = newx > wrapx and newy - h or newy
        count = newx > wrapx and 0 or count + 1
        self.positions[i] = vec2(newx, newy)
        i = i+1
    end

end

function coloredtext:draw()
    for i,v in ipairs(self.positions) do
        fill(self.colors[i])
        text(self.strings[i], v.x, v.y)
    end
end

function coloredtext:update()
    self.timer = self.timer + DeltaTime
    if self.timer > self.nextshake then
        local char = self.positions[math.random(#self.positions)]
        local t1 = tween(0.025, char, {char.x-2, char.y+2})
        local t2 = tween(0.025, char, {char.x, char.y})
        tween.sequence(t1, t2)
        self.timer = 0
    end
end

--warning: setting self.nextshake very low results in repeated tweens being applied to the same character
--causing it to not properly reset in time for the next tween
function coloredtext:update2()
    self.timer=self.timer+DeltaTime
    if self.timer > self.nextshake then
        local char = self.positions[self.shakeindex]
        local t1 = tween(0.025, char, {char.x-2, char.y+2})
        local t2 = tween(0.025, char, {char.x, char.y})
        tween.sequence(t1, t2)
        self.timer = 0
        self.shakeindex=self.shakeindex+self.shakedirection
        if self.shakeindex >= #self.positions then 
            self.shakedirection=-1
        elseif self.shakeindex <= 1 then
            self.shakedirection=1
        end
    end
end
-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(40, 40, 50)

    -- This sets the line thickness
    strokeWidth(5)
    

    -- Do your drawing here
    test0:update() --this shakes random characters
    test0:draw()
    
    test2:update2() --shakes in order by index
    test2:draw()
end


teststring = 
"this is a very long string this is a very long stringthis is a very long string this is a very long stringthis is a very long string this is a very long stringthis is a very long string this is a very long string"

@xThomas Here’s something I wrote long ago that’s kind of similar. It could easily be altered to show text in different sizes and different colors using color values instead of a color table…

displayMode(FULLSCREEN)

function setup()
    textMode(CORNER)
    str="¥aThis ¥cis ¥ddiff¥aerent ¥ccolored ¥dtext. You ¥bcan setup ¥aas many different ¥dcolors as you want. You can pick ¥athe screen ¥dx,y position and ¥ethe width to print. The colors are ¥ajust a matter ¥dof creating ¥ba ¥dlarger table of ¥ediff¥berent colors ¥dand adding ¥fthe control ¥bcharacters."
    col={a=color(255,0,0),
         b=color(0,255,0),
         c=color(0,0,255),
         d=color(255,255,0),
         e=color(0, 238, 255, 255),
         f=color(255, 0, 223, 255),
        }
end

function draw()
    background(40, 40, 50)
    showText(100,200,400)
    showText(200,600,150)
    showText(650,650,60)
end

function showText(x,y,size)
    local count, xoffset, yoffset=0,0,0
    for z=1,str:len() do
        local b1,b2=str:byte(z,z+1)
        if count==0 then
            local ch=str:sub(z,z)
            if b1==194 and b2==165 then
                fill(col[str:sub(z+2,z+2)])
                count=2
            else
                text(ch,x+xoffset,y-yoffset)
                local w,h=textSize(ch)
                xoffset = xoffset + w
                if xoffset>size then
                    xoffset=0
                    yoffset = yoffset + h
                end
                count=0
            end
        else
            count=count-1
        end
    end
end

@xThomas Here’s an example that handles the [color=xxxxxx] syntax that you show above. This can be modified to do the other [bracket command] parameters that would do text size and font differences on the same text line. I didn’t see any point in adding that code since I would never use it. You can add the updates if you really want them.

function setup()
    str="[color=#0000ff]Hello World![/color][color=#ff00ff]Goodbye World![/color]  [color=#00ff00]Mary had a little lamb[/color][color=#ff3456]it's fleece was white as snow.[/color][color=#0000ff]And everywhere that Mary went[/color][color=#ff00ff]the lamb was sure to go.[/color][color=#00ff00]He followed her to school one day[/color][color=#ff3456]which was against the rule.[/color]"
end

function draw()
    background(0)
    parse(WIDTH/2,HEIGHT-100)
end

function parse(x,y)
    offset=0
    for _,a,_,b,_,_,_ in string.gmatch(str,"(%[)(.-)(%])(.-)(%[/)(.-)(%])") do
        str1=b
        offset=offset+40
        for _,r,g,b in string.gmatch(a,"(color=#)(%w%w)(%w%w)(%w%w)") do
            fill(tonumber("0x"..r),tonumber("0x"..g),tonumber("0x"..b))       
        end
        text(str1,x,y-offset)
    end
end

@xThomas I didn’t have anything better to do, so here’s another version that does the following commands, color, size, bold, italic, bold and italic. You can add more if you want.

function setup()
    str="[color=#0000ff]Blue Regular[/color][size=32][color=#ff0000]Red size 32[/color][/size][color=#00ff00][b][i]Green Bold Italic[/i][/b][/color][color=#00ffff][size=45]Cyan size 45[/size][/color][color=#ffff00][i]Yellow Italic[/i][/color]"

    rFont="Georgia" -- regular font
    iFont="Georgia-Italic"
    bFont="Georgia-Bold"
    biFont="Georgia-BoldItalic"

    fSize=17    -- font size
    fontSize(fSize)
end

function draw()
    background(223, 175, 181, 255)
    parse(200,600)
end

function parse(x,y)
    local offset=0
    local str1,str2="",""
    for z=1,#str do
        if not bold and not italic then
            font(rFont)
        end
        c=string.sub(str,z,z)
        if c=="[" then
            st=true
            fnd=false
            if str1~="" then
                offset=offset+50
                text(str1,x,y-offset)
                str1=""
            end
            str2=str2..c
        elseif c=="]" then
            st=false
            str2=str2..c
            if str2~="" then
                set(str2)
                str2=""
            end
            fnd=true
        elseif st then
            str2=str2..c
        elseif fnd then   
            str1=str1..c
        end
    end
end

function set(st)
    s,e=string.find(st,"%[color=#") -- color
    if s~=nil then
        r=tonumber("0x"..string.sub(st,s+8,s+9))
        g=tonumber("0x"..string.sub(st,s+10,s+11))
        b=tonumber("0x"..string.sub(st,s+12,s+13))
        fill(r,g,b)
    end
    
    s,e=string.find(st,"%[b")   -- start bold
    if s~=nil then
        bold=true
        font(bFont)
    end
    s,e=string.find(st,"%[/b")  -- end bold
    if s~=nil then
        bold=false
    end

    s,e=string.find(st,"%[i")   -- start italic
    if s~=nil then
        italic=true
        font(iFont)
    end
    s,e=string.find(st,"%[/i")  -- end italic
    if s~=nil then
        italic=false
    end
    
    if bold and italic then
        font(biFont)  -- bold and italic
    end

    s,e=string.find(st,"%[size=")  -- start size
    if s~=nil then
        sz=string.sub(st,s+6,s+7)
        fontSize(sz)
    end
    s,e=string.find(st,"%[/size")  -- end size
    if s~=nil then
        fontSize(fSize)
    end
end

Thank you. I don’t have a good grasp of patterns, your code helps :slight_smile:

@xThomas It turns out that not using string.gmatch was easier to handle imbedded commands. So you don’t really need to worry about patterns.

Hmm @dave1707

Well, my internet is out, so typing this from the library (60 minute session!). Anyway. Been trying your example, and making some modifications. Not exactly sure what you mean by that not using patterns is easier, unless you’re referring to all the extra captures you were doing (i.e. wwhen you did for ,,a,, because of all the captures with the parentheses, you could just leave out the parantheses and It would be discarded. Ahh, while that works with gmatch, with gsub it is trickier - I still haven’t figured out the nuance of it (gsub is so tricky!)

i.e. i can’t figure out how to combine my two uses of gsub in the code below, so that it takes out the color code but leaves the text intact

please excuse the rushed post, I am almost out of library time :slight_smile:

… but anyway. Some code I wrote just now (don’t have my ipad with me). See the difference between how i parsed the colors and you did, im not sure if theres any actual speed difference but its easier to read…

--bbcode

teststring = "[color=#ffbc60]Hello World![/color]"
pattern =   {
     
     color ="%[color=#(%x%x)(%x%x)(%x%x)%]"
     
            }
             
for r,g,b in string.gmatch(teststring, pattern.color) do
    r,g,b = tonumber("0x"..r), tonumber("0x"..g), tonumber("0x"..b)
    print (r,g,b)
end

local newstring = string.gsub(teststring, "(%[color=#%x*%])", "")
newstring = string.gsub(newstring, "%[%/color%]", "")
for char in string.gmatch(newstring, ".") do
    print(char)
end

The above code first grabs the color from the text, then it parses out the color code leaving you with just a normal string. I did the whole ‘char in string.gmatch’ thing because I want to still use this with individual animations. I am considering some kind of caching for the color table at least though - when indexes 1 through 100 are all the same color, I don’t want to have to call 100 table.lookups when vfor example each character has its own place in the strings table, the positions table, the colors table, the rotations table, etc. I’m not actually sure how to implement

(i.e. what I have now is more like (to paraphrase my code on the ipad)

for i,v in ipairs(positions) do
  --fill(colors[i] or fill())
  if colors[i] then fill(colors[i]) end
  text(strings[i], v.x, v.y)
end

)

again sorry for the long post, but not much time left :blush:

@xThomas When I said not using string.gmatch was easier, I was referring to my last program. In that one I’m just parsing thru the string using string.sub . Using string.sub, I can identify multiple bb commands as I come across them. Using string.gmatch would have been a lot harder trying to set up all the different patterns to seperate multiple bb commands from the text. In your example, you’re identifying the color bb command. How would you set up a pattern to handle the string below that has the bb italic command inside the bold command inside the color command. That’s why I gave up using string.gmatch and switched to string.sub .

teststring = "[color=#ffbc60][b][i]Hello World![/i][/b][/color]"

Ahh, I see what you mean. I’ll think about how to solve this one when I go home