Multiline text

11 replies [Last post]
kreso
User offline. Last seen 1 year 2 weeks ago. Offline
Joined: 20 Dec 2009

I am making an app that will be showing quotes, and I need them to be in text (no pics). It took me a couple of hours so maybe it will save someone else's.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
function write_text(txt, txt_size, dim)
        --writte text into a rectangle of dimensions dim 
        --dim is table with coordinates {x1,x2,y1,y2}
        --FYI: iPhone screen dimensions are 320x480
 
        if txt_size == nil then
                txt_size = 12 --default text size if not set
        end
 
        if dim == nil then
                dim = {10,310,10,470} --default dimensions if dim is not set    
        end
        
        local w = dim[2] - dim [1]
        local h = dim[4] - dim [3]
        print(h .. w)
        
        --draw a rectanle to see the borders clearly
        whiteRect = display.newRect( dim[1], dim[3], w, h )
        whiteRect:setFillColor( 255,255,255 )
        
        local t = display.newText( "", dim[1], dim[3], nil, txt_size );
 
        --write text line by line, actually word by word.. :(
        --I will use this lmitation to my advantage and make a cool letter-for-letter animation while I'm at it :)
 
        local newlined_string = ""       --here is variable for saved (finall) string with newlines and fitted words 
        local next_word = ""             --next_word is buffer for next whole word (until SPACE or endOfString is reached), is independant of lines
        local skip_space = false
        
    -- get 1 character, add it to string, and render it
        for i = 1, #txt do
            local c = txt:sub(i,i)                                                                      --c is current character we are working with 
 
            local test_string = newlined_string .. next_word .. c       --string for testing rendered width (including half of word for exmple)
                                                                                                                        --
                local t = display.newText( test_string, dim[1], dim[3], nil, txt_size );
                --t:setTextColor( 100, 100, 136, 255 );                                 --uncomment this line to see actuall rendering  
 
                --see if rendering this character crossed our boundary
                if t.stageWidth >= w then                                                               --got outside boundary!
                        newlined_string = newlined_string .. "\n"                       --insert newline (\n)
                        skip_space = true                                                                       --remember new line so next word (line of text) doesn't start with SPACE
                end
                
                if txt:sub(i+1,i+1) == " " or i== #txt then                             --if next character is 'SPACE' or we reached end of string, add word to strng
                        if skip_space then                                                                      --new line so no need for SPACE on beginning of that line
                                skip_space = false
                                next_word = next_word:sub(2, #next_word)                --remove the first char (SPACE) from word
                        end
                        
                        newlined_string = newlined_string .. next_word .. c     --add last word + last char to string and then,
                        next_word = ""                                                                          --reset variable
                else                                                                                                    --all other characters
                        next_word = next_word .. c                                                      --push inside
                end
                
                
 
        end
        
        t = display.newText( newlined_string, dim[1], dim[3], nil, txt_size );  --finall text object
        t:setTextColor( 100, 100, 136, 255 );                                                                   --set text color here
 
 
end
 
local text = "Now while I;m making this thing I had an idea. I'll make a function/animation that will show letter-for-letter my text on screen. That sounds like fun." 
 
--USAGE:
--write_text(text)
--write_text(text, 20)
write_text(text, 15, {100,263,150,300})

Replies

chiliberto's picture
chiliberto
User offline. Last seen 8 weeks 2 days ago. Offline
Joined: 29 Oct 2009

Hi,

Have you tried your code on a device? I tried it on my iPhone and it just shows a band of color.

I have some Lua code that does something similar, and it works great in the simulator, but just shows a stripe of color instead of the text. Here's the code:

function wrap(str, limit, indent, indent1)
indent = indent or ""
indent1 = indent1 or indent
limit = limit or 72
local here = 1-#indent1
return indent1..str:gsub("(%s+)()(%S+)()",
function(sp, st, word, fi)
if fi-here > limit then
here = st - #indent
return "\n"..indent..word
end
end)
end

I posted a bug about this here: https://developer.anscamobile.com/forum/2009/12/29/text-wraps-inconsistently-simulator-and-device

Apparently, line breaks work in the simulator but not on the device yet.

I'm working on a new text wrapping function that just writes each line as a new text display object. I'll post it soon.

Cheers,
GG

ruuzo
User offline. Last seen 8 weeks 6 days ago. Offline
Joined: 19 Nov 2009

i need the same functionality in the app i'm working on so i'm eager to see what you come up with...thanks for sharing! anyone know if support for different fonts is coming?

chiliberto's picture
chiliberto
User offline. Last seen 8 weeks 2 days ago. Offline
Joined: 29 Oct 2009

OK! I've got something that seems to work pretty well. There's two functions. The first is called "wrap" and it's a helper function that adds line breaks to your text. The second is called wrappedText and it takes the text outputted from the helper function and displays everyline as text objects. It works in the simulator and on the device.

Use the second function, wrappedText, in your code. It accepts a string, character limit for wrapping, font size, font color, and indents. It outputs an group object that contains your text and that can be manipulated like any other object on the screen , e.g. myTextObject.x = 100.

One thing to note is that the API function display.newText() seems to choke on bad characters which are usually invisible. Your text must be clean. I had lots of trouble with invisible characters at the beginning of new lines in a file generated by a server-side script.

Enjoy!
GG

---8<---------------------------

-- Wrap text
function wrap(str, limit, indent, indent1)
  indent = indent or ""
  indent1 = indent1 or indent
  limit = limit or 72
  local here = 1-#indent1
  return indent1..str:gsub("(%s+)()(%S+)()",
                          function(sp, st, word, fi)
                            if fi-here > limit then
                              here = st - #indent
                              return "\n"..indent..word
                            end
                          end)
end
 
function wrappedText(str, limit, size, color, indent, indent1)
        --apply line breaks using the wrapping function
        str = wrap(str, limit, indent, indent1)
        size = size or 12
        color = color or {255, 255, 255}
 
        --search for each line that ends with a line break and add to an array
        local pos, arr = 0, {}
        for st,sp in function() return string.find(str,"\n",pos,true) end do
                table.insert(arr,string.sub(str,pos,st-1))  
                pos = sp + 1  
        end
        table.insert(arr,string.sub(str,pos))  
                         
        --iterate through the array and add each item as a display object to the group
        local g = display.newGroup()
        local i = 1
    while i <= #arr do
                local t = display.newText( arr[i], 0, 0, 0, size )    
                t:setTextColor( color[1], color[2], color[3] )
                t.xReference = -t.width/2
                t.x = 0
                t.yReference = -t.height/2
                t.y = 14*(i-1)
                g:insert(t)
                i = i + 1
        end
        return g
end

---8<---------------------------

-- USAGE:
local myText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc euismod justo sapien, at sollicitudin lacus. Quisque vestibulum commodo felis id posuere."
local myTextObject = wrappedText(myText, 46)
--local myTextObject = wrappedText(myText, 46, 16)
--local myTextObject = wrappedText(myText, 46, 16, {255, 0, 0})
local myGroup = display.newGroup()
myGroup:insert( myTextObject )

ruuzo
User offline. Last seen 8 weeks 6 days ago. Offline
Joined: 19 Nov 2009

hey thanks, much appreciated! by the way, i'm using some different size fonts and i noticed the vertical spacing was static...but i got some good results easily enough by changing this line:

t.y = 14*(i-1)

to this:

t.y = size*(i-1)

thanks, you just saved me a chunk of time...i'm going to find something in my code now to share with the world

chiliberto's picture
chiliberto
User offline. Last seen 8 weeks 2 days ago. Offline
Joined: 29 Oct 2009

Thanks, ruuzo. I just added your code with a modification for improved readability. I'm using size*1.3 rather than just size. Try it. It's much better and more readable.

I also made an update to support paragraph spaces better. The wrap() helper function was including line breaks such as "\n" in its character count, which made the beginning lines of new paragraphs too short. With the addition of another helper function, explode(), that is not longer a problem. Here's the new code:

----8<------------------------

-- Wrap text
<code>function wrap(str, limit, indent, indent1)
  indent = indent or ""
  indent1 = indent1 or indent
  limit = limit or 72
  local here = 1-#indent1
  return indent1..str:gsub("(%s+)()(%S+)()",
                          function(sp, st, word, fi)
                            if fi-here > limit then
                              here = st - #indent
                              return "\n"..indent..word
                            end
                          end)
end
 
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
    table.insert(arr,string.sub(str,pos,st-1)) -- Attach chars left of current divider
    pos = sp + 1 -- Jump past current divider
  end
  table.insert(arr,string.sub(str,pos)) -- Attach chars right of last divider
  return arr
end
 
 
function wrappedText(str, limit, size, color, indent, indent1)
        str = explode("\n", str)
 
        --apply line breaks using the wrapping function
        local i = 1
        local strFinal = ""
    while i <= #str do
                strW = wrap(str[i], limit, indent, indent1)
                size = size or 12
                color = color or {255, 255, 255}
                strFinal = strFinal.."\n"..strW
                i = i + 1
        end
        str = strFinal
       
        --search for each line that ends with a line break and add to an array
        local pos, arr = 0, {}
        for st,sp in function() return string.find(str,"\n",pos,true) end do
                table.insert(arr,string.sub(str,pos,st-1))
                pos = sp + 1
        end
        table.insert(arr,string.sub(str,pos))
                       
        --iterate through the array and add each item as a display object to the group
        local g = display.newGroup()
        local i = 1
    while i <= #arr do
                local t = display.newText( arr[i], 0, 0, 0, size )    
                t:setTextColor( color[1], color[2], color[3] )
                t.xReference = -t.width/2
                t.x = 0
                t.yReference = -t.height/2
                t.y = (size*1.3)*(i-1)
                g:insert(t)
                i = i + 1
        end
        return g
end
 

----8<------------------------

-- USAGE:
local myText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc euismod justo sapien, at sollicitudin lacus. Quisque vestibulum commodo felis id posuere."
local myTextObject = wrappedText(myText, 46)
--local myTextObject = wrappedText(myText, 46, 16)
--local myTextObject = wrappedText(myText, 46, 16, {255, 0, 0})
local myGroup = display.newGroup()
myGroup:insert( myTextObject )

chiliberto's picture
chiliberto
User offline. Last seen 8 weeks 2 days ago. Offline
Joined: 29 Oct 2009

By the way, this function works best with line breaks that are all \n . So be sure to replace any \r with \n before sending your text to this function . For example, in PHP use $str = str_replace("\r", "\n", $str)

Cheers,
GG

kreso
User offline. Last seen 1 year 2 weeks ago. Offline
Joined: 20 Dec 2009

Actually I haven't tried it on the iPhone, I'm still waiting for my activation code.

Hopefully next update will solve this issue so we don't have to use these workarounds. Until then, it's good that you found a way to make it work.

@Ansca people: I hope we get text tools available as soon as possible (including text input - keyboard)!

Jeff Johnson's picture
Jeff Johnson
User offline. Last seen 1 year 28 weeks ago. Offline
Joined: 3 Jun 2009

I promise, its a top priority. ;)

Gilbert's picture
Gilbert
User offline. Last seen 3 weeks 6 days ago. Offline
Ansca Staff
Joined: 5 Apr 2010

I've added support for fonts and text color when you're wrapping text. Here's the new code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
---8<-----------------------------------------
-- Wrap text
function wrap(str, limit, indent, indent1)
  indent = indent or ""
  indent1 = indent1 or indent
  limit = limit or 72
  local here = 1-#indent1
  return indent1..str:gsub("(%s+)()(%S+)()",
                          function(sp, st, word, fi)
                            if fi-here > limit then
                              here = st - #indent
                              return "\n"..indent..word
                            end
                          end)
end
 
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
    table.insert(arr,string.sub(str,pos,st-1)) -- Attach chars left of current divider
    pos = sp + 1 -- Jump past current divider
  end
  table.insert(arr,string.sub(str,pos)) -- Attach chars right of last divider
  return arr
end
 
 
function wrappedText(str, limit, size, font, color, indent, indent1)
        str = explode("\n", str)
        size = tonumber(size) or 12
        color = color or {255, 255, 255}
        font = font or "Helvetica"      
 
        --apply line breaks using the wrapping function
        local i = 1
        local strFinal = ""
    while i <= #str do
                strW = wrap(str[i], limit, indent, indent1)
                strFinal = strFinal.."\n"..strW
                i = i + 1
        end
        str = strFinal
        
        --search for each line that ends with a line break and add to an array
        local pos, arr = 0, {}
        for st,sp in function() return string.find(str,"\n",pos,true) end do
                table.insert(arr,string.sub(str,pos,st-1)) 
                pos = sp + 1 
        end
        table.insert(arr,string.sub(str,pos)) 
                        
        --iterate through the array and add each item as a display object to the group
        local g = display.newGroup()
        local i = 1
    while i <= #arr do
                local t = display.newText( arr[i], 0, 0, font, size )    
                t:setTextColor( color[1], color[2], color[3] )
                t.x = math.floor(t.width/2)
                t.y = (size*1.3)*(i-1)
                g:insert(t)
                i = i + 1
        end
        return g
end
---8<----------------------------------------- 
 
-- USAGE: 
local myText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc euismod justo sapien, at sollicitudin lacus. Quisque vestibulum commodo felis id posuere."
local myTextObject = wrappedText( myText, 46, 16, "Helvetica", {255, 0, 0} )
local myGroup = display.newGroup()
myGroup:insert( myTextObject )

jmartinho
User offline. Last seen 1 week 3 days ago. Offline
Joined: 25 Mar 2010

Gilbert

thanks for your code. Great.

As what I could test, it only supports one orientation. If you rotate it, it disapears.

A good improvement is the ability to rotate the text box

thks
j

genkilabs
User offline. Last seen 1 year 29 weeks ago. Offline
Joined: 4 Jul 2010

@Gilbert - Great! That is exactly what I wanted. Thanks so much.

Viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.