Improved text layout system

Hi folks,
I’ve had a lot of fun recently with Lua and the Corona SDK but I noticed that the text support (whilst good) did appear lacking in terms of layout options and general “rich text” support - so instead of plumping for a Webview, I’ve put together a function that takes as input a table of data to render as text, but will also recognise certain “commands” so that you can change the font, size, colour, margins etc.

The code should be self explanatory (all the commands start with a “#” and are stored as separate table entries, any parameters to the commands (ie the colour RGB values) are stored in the table as separate entries (the commands should know how many parameters are required). Ultimately each “word” is rendered as a separate text object and added to a display group - the group is then returned and due to the excellent display hierarchy system can be manipulated as a single unit (for scrolling / panning etc)

Extending / personalising the command list should be trivial - as would be changing the font types etc.

Hopefully this code will be of use to someone, possibly for laying out help screens or in game text using multiple fonts / colours etc.

  
local gameGroup = display.newGroup()  
local textGroup = display.newGroup()  
  
-- --------------------------------------------------------------------------------------------------  
-- ==================================================================================================  
-- --------------------------------------------------------------------------------------------------  
  
local displayList = {  
 "Hello world, this is some \"text\" that I want to display it's a very long line that should wrap over several lines",  
 "#brn",45, -- line break with a variable height  
 "#cl",255,128,0, -- set the colour  
 "Normal text",  
 "#bl", -- bold text  
 "Bold text",   
 "#nm", -- normal text  
 "#cl",255,0,0,  
 "more normal text",   
 "#lm",20,"#rm",280, -- set the left and right margins  
 "#br",   
 "The line feed character shouldn't be used to split lines",  
 "#cla",0,0,0,128, -- set the colour (and alpha)  
 "Or a paragraph marker would also work as well",  
 "#lm",0,"#rm",320,  
 "#br",  
 "#cl",0,0,0,  
 "The moving finger writes and having written moves on",  
 "and on and on and on...",  
}  
  
-- --------------------------------------------------------------------------------------------------  
-- --------------------------------------------------------------------------------------------------  
  
local currentFont = "Verdana"  
local currentSize = 20  
local currentColR = 0  
local currentColG = 0  
local currentColB = 0  
local currentColA = 255  
local currentAlign = 0  
local leading = 0  
local spaceWidth = -3   
local xLeft = 0  
local xRight = 320  
  
local lastX, lastY, lastHeight  
  
-- --------------------------------------------------------------------------------------------------  
  
function explode(div,str)  
 if (div=='') then return false end  
 local pos,arr = 0,{}  
 -- for each divider found  
 for st,sp in function() return string.find(str,div,pos,true) end do  
 -- Attach chars left of current divider  
 -- table.insert(arr,string.sub(str,pos,st-1))   
 arr[#arr+1] = string.sub(str,pos,st-1)  
 pos = sp + 1 -- Jump past current divider  
 end  
 -- Attach chars right of last divider  
 -- table.insert(arr,string.sub(str,pos))   
 arr[#arr+1] = string.sub(str,pos)   
 return arr  
end  
  
-- --------------------------------------------------------------------------------------------------  
  
function makeLines(strings,group)   
  
 strings = explode(" ",strings)  
 local num = #strings  
 local tlx,w,word  
  
 for i=1,num do  
  
 word = display.newText(strings[i],0,0,currentFont,currentSize)  
 word:setReferencePoint(display.TopLeftReferencePoint)  
 word:setTextColor(currentColR,currentColG,currentColB,currentColA)  
 w = word.width  
 lastHeight = word.height  
  
 tlx = lastX + w  
  
 if tlx \> xRight then lastY = lastY + lastHeight; lastX = xLeft end  
  
 word.x = lastX; word.y = lastY; lastX = lastX + (w + spaceWidth)  
  
 group:insert(word)  
 end  
end  
  
-- --------------------------------------------------------------------------------------------------  
-- --------------------------------------------------------------------------------------------------  
-- Generate a text group that contains several other text groups formatted as per the instructions  
-- in the display list...  
  
local function newTextLayout(list)  
  
 local index,data,t  
 local listLen = #list  
 local g = display.newGroup()  
  
 lastX, lastY = 0,0  
  
 -- print("Listlength = " .. listLen)  
  
 index = 1  
  
 while index \<= listLen do  
  
 data = list[index] index = index + 1  
  
 -- Check data to see if it's a valid command (add your own as required)   
 if data == "#br" then lastX = xLeft; lastY = lastY + lastHeight  
 elseif data == "#brn" then t = tonumber(list[index]) lastX = xLeft; lastY = lastY + t; index = index + 1  
 elseif data == "#lm" then xLeft = tonumber(list[index]); index = index + 1   
 elseif data == "#rm" then xRight = tonumber(list[index]); index = index + 1   
  
 elseif data == "#bl" then currentFont = "Helvetica"; currentSize = 25;   
 elseif data == "#nm" then currentFont = "Verdana"; currentSize = 20;  
  
 elseif data == "#cl" then currentColR = tonumber(list[index]); currentColG = tonumber(list[index+1]); currentColB = tonumber(list[index+2]); currentColA = 255; index = index + 3  
 elseif data == "#cla" then currentColR = tonumber(list[index]); currentColG = tonumber(list[index+1]); currentColB = tonumber(list[index+2]); currentColA = tonumber(list[index+3]); index = index + 4  
 else   
 -- Else it's a string to render into a block of text groups   
 makeLines(data,g)  
 end  
  
 end  
  
 return g  
end  
  
-- --------------------------------------------------------------------------------------------------  
-- --------------------------------------------------------------------------------------------------  
  
local plat = display.newRect(0,0,320,480)  
plat:setReferencePoint(display.TopLeftReferencePoint)  
gameGroup:insert(plat)  
gameGroup:insert(textGroup)  
  
textGroup:insert(newTextLayout(displayList))  
  
print("Text group ".. textGroup.width ..",".. textGroup.height)  

Comments, suggestions and improvements welcome.
I’ve only just started with Lua so if anyone’s got any feedback on how to improve my Lua let’s hear 'em.

Apologies if this post is in the wrong section - hopefully a mod can move it and post a re-direction.

Jon… [import]uid: 7901 topic_id: 8406 reply_id: 308406[/import]

NICE !

thanks

Jon, you should zip it up and also post it on our code exchange pages.

C [import]uid: 24 topic_id: 8406 reply_id: 30102[/import]

Will do (or rather will do as soon as I get my proper subscription sorted out - I’m still using the Windows trial edition at the moment).

Oops :sunglasses: [import]uid: 7901 topic_id: 8406 reply_id: 30115[/import]

I tried using this code on a project but found it didn’t work with Corona’s auto-scaling. I’ll post here the code that works with the scaling, first is the code for “TextLayout.lua” and on the next post is the “main.lua” code [import]uid: 79152 topic_id: 8406 reply_id: 94869[/import]

[lua]module(…,package.seeall)

local DisplayObjectPrototype = require “DisplayObjectPrototype”
DisplayObjectPrototype:generalize(TextLayout)

local fonte = native.systemFont --native.newFont( “Adobe Garamond Pro”, 20 )

local fonteBold = native.systemFontBold–native.newFont( “Adobe Garamond Pro Bold”, 20 )

@Override
function TextLayout:init(properties)
self.view = display.newGroup()

self.xRight = properties.xRight
self.currentSize = properties.currentSize

self:newTextLayout(properties.textList)
end
function TextLayout:explode(div,str)

if (div==’’) then return false end

local pos,arr = 0,{}

– for each divider found

for st,sp in function() return string.find(str,div,pos,true) end do

– Attach chars left of current divider

– table.insert(arr,string.sub(str,pos,st-1))

arr[#arr+1] = string.sub(str,pos,st-1)

pos = sp + 1 – Jump past current divider

end

– Attach chars right of last divider

– table.insert(arr,string.sub(str,pos))

arr[#arr+1] = string.sub(str,pos)

return arr

end



– Generate a text group that contains several other text groups formatted as per the instructions

– in the display list…

function TextLayout:newTextLayout(list)
local currentFont = fonte

local currentSize = self.currentSize or 20

local currentColR = 0

local currentColG = 0

local currentColB = 0

local currentColA = 255

local currentAlign = 0

local leading = 0

local spaceWidth = 2

local xLeft = 0

local xRight = self.xRight or 320

local lastX = nil
local lastY = nil
local lastHeight = nil
function self:makeLines(strings)

local group = self.view

strings = self:explode(" ",strings)

local num = #strings

local tlx,w,word

for i=1,num do

word = display.newRetinaText(strings[i],0,0,currentFont,currentSize)

word:setReferencePoint(display.TopLeftReferencePoint)

word:setTextColor(currentColR,currentColG,currentColB,currentColA)

–w = word.width

–lastHeight = word.height
lastHeight = word.contentBounds.yMax - word.y

word.x = lastX
–tlx = lastX + w

tlx = word.contentBounds.xMax

if tlx > xRight then
lastY = lastY + lastHeight
lastX = xLeft
end

word.x = lastX; word.y = lastY; --[[lastX = lastX + (w + spaceWidth)]] lastX = word.contentBounds.xMax + spaceWidth

group:insert(word)

end

end

local index,data,t

local listLen = #list

local g = display.newGroup()

lastX, lastY = 0,0

– print("Listlength = " … listLen)

index = 1

while index <= listLen do

data = list[index] index = index + 1

– Check data to see if it’s a valid command (add your own as required)

if data == “#br” then lastX = xLeft; lastY = lastY + lastHeight

elseif data == “#brn” then t = tonumber(list[index]) lastX = xLeft; lastY = lastY + t; index = index + 1

elseif data == “#lm” then xLeft = tonumber(list[index]); index = index + 1

elseif data == “#rm” then xRight = tonumber(list[index]); index = index + 1

elseif data == “#bl” then currentFont = fonteBold; currentSize = self.currentSize;

elseif data == “#nm” then currentFont = fonte; currentSize = self.currentSize;

elseif data == “#cl” then currentColR = tonumber(list[index]); currentColG = tonumber(list[index+1]); currentColB = tonumber(list[index+2]); currentColA = 255; index = index + 3

elseif data == “#cla” then currentColR = tonumber(list[index]); currentColG = tonumber(list[index+1]); currentColB = tonumber(list[index+2]); currentColA = tonumber(list[index+3]); index = index + 4

else

– Else it’s a string to render into a block of text groups

self:makeLines(data,g)

end

end

return g

end[/lua] [import]uid: 79152 topic_id: 8406 reply_id: 94870[/import]

[lua]–
– Project: Text Layout
– Description:

– Version: 1.255
– Managed with http://CoronaProjectManager.com

local TextLayout = require"TextLayout"

function main()

local text = TextLayout:new({textList = {“Hello world, this is some “text” that I want to display it’s a very long line that should wrap over several lines”,
#brn”,45, – line break with a variable height
#cl”,255,255,255, – set the colour
“Normal text”,
#bl”, – bold text
“Bold text”,
#nm”, – normal text
#cl”,255,255,255,
“more normal text”,
#lm”,20,"#rm",360, – set the left and right margins
#br”,
“The line feed character shouldn’t be used to split lines”,
#cla”,255,255,255,255, – set the colour (and alpha)
“Or a paragraph marker would also work as well”,
#lm”,0,"#rm",640,
#br”,
#cl”,255,255,255,
“The moving finger writes and having written moves on”,
“and on and on and on…”,}, xRight=600, currentSize=40 })
text.view:setReferencePoint(display.CenterReferencePoint)
text.view.x = display.contentCenterX
text.view.y = display.contentCenterY
end

main()[/lua] [import]uid: 79152 topic_id: 8406 reply_id: 94871[/import]

sorry for not using sample code and all, but couldn’t get away from my project to set up the github [import]uid: 79152 topic_id: 8406 reply_id: 94872[/import]

this solution works poorly if there is a lot of text, I guess cause it generates loads of objects for corona to handle.

My solution was to isolate the paragraphs that had formatting and do the rest w/ regular texts [import]uid: 79152 topic_id: 8406 reply_id: 94997[/import]