Find the number of lines in text?

Hey,

I have a text input box make a text object to display it. I set the width to my screen bounds but I have no way of seeing how many lines a given input will take up.

I tried this by counting characters but it didn’t work. For example the “W” and “i” have different widths so it was inconsistent. 

What’s a good way to do this?

I suppose you could use a monospaced font.

This is a very interesting question, i.e. “How to predict width/height of text object before creating it.”  I don’t know how to do this, but I hope someone does.  This might be one of those black magic things, or you may need to do some brute force work.

Note: I’m commenting not only to show interest, but to push this to the top of the active list so it gets more eyeballs.

Cheers,

Ed

I threw together this and it seems to be working like a charm:

-- round any number to a given decimal placelocal function round(num, idp)     local mult = 10^(idp or 0)     return mathFloor(num \* mult + 0.5) / mult end

-- localize math.ceil local mathCeil = math.ceil

local function findLines (text) &nbsp; &nbsp; -- you can put in any width bound you want here &nbsp; &nbsp; local widthBound = screenWidth &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; -- create a temp text object without any bounds &nbsp; &nbsp; local taskOptions = &nbsp; &nbsp; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;text = text, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;-- the given text &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;font = "OpenSans", &nbsp; &nbsp; &nbsp; -- your font &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;fontSize = 45, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;-- your font's size &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;align = "left" &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;-- aligned to the left &nbsp; &nbsp; } &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; -- make our temp text using the above options &nbsp; &nbsp; local taskText = display.newText(taskOptions ) &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; -- get the final width of our temp text object &nbsp; &nbsp; local textWidth = taskText.width &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; -- find the ratio of our text's width to our widthBound &nbsp; &nbsp; local ratio = textWidth / widthBound &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; -- round it off to one decimal place &nbsp; &nbsp; local roundedRatio = round (ratio,1) &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; -- if our ratio is less than our bound, it can fit onto one line &nbsp; &nbsp; if (textWidth \<= widthBound) then &nbsp; &nbsp; &nbsp; &nbsp; -- remove our temp text &nbsp; &nbsp; &nbsp; &nbsp; taskText:removeSelf() &nbsp; &nbsp; &nbsp; &nbsp; taskText = nil &nbsp; &nbsp; &nbsp; &nbsp; -- return 1 line &nbsp; &nbsp; &nbsp; &nbsp; return 1 &nbsp; &nbsp; end &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; -- if our ratio is greater than our bound, it will be multiple lines &nbsp; &nbsp; local numOfLines &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; -- find the number of lines by math.ceil our rounded ratio &nbsp; &nbsp; if (ratio \< roundedRatio) then &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;numOfLines = mathCeil(roundedRatio + .01 ) &nbsp; &nbsp; else &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;numOfLines = mathCeil(roundedRatio ) &nbsp; &nbsp; end &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; -- remove our temp text &nbsp; &nbsp; taskText:removeSelf() &nbsp; &nbsp; taskText = nil &nbsp; &nbsp; -- return the number of lines &nbsp; &nbsp; return numOfLines end

Well, if I can come up with something that is of interest to the famous @roaminggamer, then that will be a very good day indeed! :slight_smile:

This may not be the world’s most elegant solution (it basically creates an invisible temporary newText, grabs the data we need, and then erases it), but I whipped up a quick module that I’m calling “textSpace.” It sets up a function textSpace.get() that you supply with the following:

  1. The string you’ll be plugging into your display.newText()
  2. The font you want to use
  3. The font size you want to use
  4. The desired width of your display.newText()

The function returns a table with two values: “numRows,” the number of rows in your desired display.newText() and “pixelHeight” which is the height of your desired display.newText() in pixels. Here is the module, which you should save as textSpace.lua and put in your project’s root directory:

local textSpace = {} ------------------------------------------------------------------------------------ -- DECLARE PLACEMENT VARIABLES (NOT ESSENTIAL, JUST MY OWN SHORTCUTS) ------------------------------------------------------------------------------------ local centerX = display.contentCenterX local centerY = display.contentCenterY local screenTop = display.screenOriginY local screenLeft = display.screenOriginX local screenBottom = display.screenOriginY+(display.contentHeight-(display.screenOriginY\*2)) local screenRight = display.screenOriginX+(display.contentWidth-(display.screenOriginX\*2)) local screenWidth = screenRight - screenLeft local screenHeight = screenBottom - screenTop ------------------------------------------------------------------------------------ -- GET ROW # & PIXEL HEIGHT FOR STRING WITH WIDTH ------------------------------------------------------------------------------------ function textSpace.get(string, font, fontSize, width) -- set width as screenWidth if not specified if string == nil or font == nil or fontSize == niln or width == nil then print("TEXTSPACE ERROR: You must supply four arguments when calling textSpace.get() - string, font, fontSize, & width") return true end -- calculate height of one row & height of whole text box local tempRow = display.newText(" ", 0, 0, font, fontSize) tempRow.isVisible = false local rowHeight = tempRow.height local tempText = display.newText(string, 0, 0, width, 0, font, fontSize) tempText.isVisible = false local pixelHeight = tempText.height local numRows = math.ceil(pixelHeight/rowHeight) display.remove(tempRow) tempRow = nil display.remove(tempText) tempText = nil -- return a table with the number of rows & pixel height return {["numRows"] = numRows, ["pixelHeight"] = pixelHeight} end return textSpace

And here is a sample main.lua that uses the module to successfully calculate the number of rows of a generic string of text. I tested this in the simulator using multiple device skins, and multiple strings, fonts, font sizes, etc, and it always returned the right data:

local textSpace = require("textSpace") ------------------------------------------------------------------------------------ -- DECLARE PLACEMENT VARIABLES (NOT ESSENTIAL, JUST MY OWN SHORTCUTS) ------------------------------------------------------------------------------------ local centerX = display.contentCenterX local centerY = display.contentCenterY local screenTop = display.screenOriginY local screenLeft = display.screenOriginX local screenBottom = display.screenOriginY+(display.contentHeight-(display.screenOriginY\*2)) local screenRight = display.screenOriginX+(display.contentWidth-(display.screenOriginX\*2)) local screenWidth = screenRight - screenLeft local screenHeight = screenBottom - screenTop ------------------------------------------------------------------------------------ -- DECLARE VARIABLES FOR OUR DISPLAY.NEWTEXT() ------------------------------------------------------------------------------------ local string = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." local font = native.systemFont local fontSize = 20 local width = screenWidth ------------------------------------------------------------------------------------ -- PRE-CALCULATE NUMBER OF ROWS & PIXEL HEIGHT ------------------------------------------------------------------------------------ local space = textSpace.get(string, font, fontSize, width) print("NUMBER OF ROWS: "..space.numRows) print("HEIGHT IN PIXELS: "..space.pixelHeight) ------------------------------------------------------------------------------------ -- CREATE AN ACTUAL DISPLAY.NEWTEXT() AND SEE IF WE GOT THE RIGHT NUMBER... ------------------------------------------------------------------------------------ test = display.newText(string, centerX, centerY, width, 0, font, fontSize) print("actual pixelHeight on-screen: "..test.height)

Hope this helps!

I think you can condense it by using the table options for display.newText. Also, I found I had to floor the division to get the correct number of lines. I tried with the system font and a custom one.

[lua]

– options is a table with the fields: text, align, x, y, font, fontSize, width, parent

function getTextRowsHeight(options)

  local txt=options.text

  options.text=" "

  local tempRow = display.newText(options)

  local rowHeight = tempRow.height

  

  options.text=txt

  local tempText = display.newText(options)

  local h=tempText.height  

  local numRows = math.floor(h/rowHeight)

  

  tempText:removeSelf()

  tempRow:removeSelf()

  – return a table with the number of rows & pixel height

  return {numRows=numRows, height=h}

end
[/lua]

tap32’s function works and should be the preferred answer - but even simpler is to not specify a height and use it later to move the text in place (and increase some y position for any stuff that should be below or around the text box). Mark as Solved!

I suppose you could use a monospaced font.

This is a very interesting question, i.e. “How to predict width/height of text object before creating it.”  I don’t know how to do this, but I hope someone does.  This might be one of those black magic things, or you may need to do some brute force work.

Note: I’m commenting not only to show interest, but to push this to the top of the active list so it gets more eyeballs.

Cheers,

Ed

I threw together this and it seems to be working like a charm:

-- round any number to a given decimal placelocal function round(num, idp) &nbsp; &nbsp; local mult = 10^(idp or 0) &nbsp; &nbsp; return mathFloor(num \* mult + 0.5) / mult end

-- localize math.ceil local mathCeil = math.ceil

local function findLines (text) &nbsp; &nbsp; -- you can put in any width bound you want here &nbsp; &nbsp; local widthBound = screenWidth &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; -- create a temp text object without any bounds &nbsp; &nbsp; local taskOptions = &nbsp; &nbsp; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;text = text, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;-- the given text &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;font = "OpenSans", &nbsp; &nbsp; &nbsp; -- your font &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;fontSize = 45, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;-- your font's size &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;align = "left" &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;-- aligned to the left &nbsp; &nbsp; } &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; -- make our temp text using the above options &nbsp; &nbsp; local taskText = display.newText(taskOptions ) &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; -- get the final width of our temp text object &nbsp; &nbsp; local textWidth = taskText.width &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; -- find the ratio of our text's width to our widthBound &nbsp; &nbsp; local ratio = textWidth / widthBound &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; -- round it off to one decimal place &nbsp; &nbsp; local roundedRatio = round (ratio,1) &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; -- if our ratio is less than our bound, it can fit onto one line &nbsp; &nbsp; if (textWidth \<= widthBound) then &nbsp; &nbsp; &nbsp; &nbsp; -- remove our temp text &nbsp; &nbsp; &nbsp; &nbsp; taskText:removeSelf() &nbsp; &nbsp; &nbsp; &nbsp; taskText = nil &nbsp; &nbsp; &nbsp; &nbsp; -- return 1 line &nbsp; &nbsp; &nbsp; &nbsp; return 1 &nbsp; &nbsp; end &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; -- if our ratio is greater than our bound, it will be multiple lines &nbsp; &nbsp; local numOfLines &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; -- find the number of lines by math.ceil our rounded ratio &nbsp; &nbsp; if (ratio \< roundedRatio) then &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;numOfLines = mathCeil(roundedRatio + .01 ) &nbsp; &nbsp; else &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;numOfLines = mathCeil(roundedRatio ) &nbsp; &nbsp; end &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; -- remove our temp text &nbsp; &nbsp; taskText:removeSelf() &nbsp; &nbsp; taskText = nil &nbsp; &nbsp; -- return the number of lines &nbsp; &nbsp; return numOfLines end

Well, if I can come up with something that is of interest to the famous @roaminggamer, then that will be a very good day indeed! :slight_smile:

This may not be the world’s most elegant solution (it basically creates an invisible temporary newText, grabs the data we need, and then erases it), but I whipped up a quick module that I’m calling “textSpace.” It sets up a function textSpace.get() that you supply with the following:

  1. The string you’ll be plugging into your display.newText()
  2. The font you want to use
  3. The font size you want to use
  4. The desired width of your display.newText()

The function returns a table with two values: “numRows,” the number of rows in your desired display.newText() and “pixelHeight” which is the height of your desired display.newText() in pixels. Here is the module, which you should save as textSpace.lua and put in your project’s root directory:

local textSpace = {} ------------------------------------------------------------------------------------ -- DECLARE PLACEMENT VARIABLES (NOT ESSENTIAL, JUST MY OWN SHORTCUTS) ------------------------------------------------------------------------------------ local centerX = display.contentCenterX local centerY = display.contentCenterY local screenTop = display.screenOriginY local screenLeft = display.screenOriginX local screenBottom = display.screenOriginY+(display.contentHeight-(display.screenOriginY\*2)) local screenRight = display.screenOriginX+(display.contentWidth-(display.screenOriginX\*2)) local screenWidth = screenRight - screenLeft local screenHeight = screenBottom - screenTop ------------------------------------------------------------------------------------ -- GET ROW # & PIXEL HEIGHT FOR STRING WITH WIDTH ------------------------------------------------------------------------------------ function textSpace.get(string, font, fontSize, width) -- set width as screenWidth if not specified if string == nil or font == nil or fontSize == niln or width == nil then print("TEXTSPACE ERROR: You must supply four arguments when calling textSpace.get() - string, font, fontSize, & width") return true end -- calculate height of one row & height of whole text box local tempRow = display.newText(" ", 0, 0, font, fontSize) tempRow.isVisible = false local rowHeight = tempRow.height local tempText = display.newText(string, 0, 0, width, 0, font, fontSize) tempText.isVisible = false local pixelHeight = tempText.height local numRows = math.ceil(pixelHeight/rowHeight) display.remove(tempRow) tempRow = nil display.remove(tempText) tempText = nil -- return a table with the number of rows & pixel height return {["numRows"] = numRows, ["pixelHeight"] = pixelHeight} end return textSpace

And here is a sample main.lua that uses the module to successfully calculate the number of rows of a generic string of text. I tested this in the simulator using multiple device skins, and multiple strings, fonts, font sizes, etc, and it always returned the right data:

local textSpace = require("textSpace") ------------------------------------------------------------------------------------ -- DECLARE PLACEMENT VARIABLES (NOT ESSENTIAL, JUST MY OWN SHORTCUTS) ------------------------------------------------------------------------------------ local centerX = display.contentCenterX local centerY = display.contentCenterY local screenTop = display.screenOriginY local screenLeft = display.screenOriginX local screenBottom = display.screenOriginY+(display.contentHeight-(display.screenOriginY\*2)) local screenRight = display.screenOriginX+(display.contentWidth-(display.screenOriginX\*2)) local screenWidth = screenRight - screenLeft local screenHeight = screenBottom - screenTop ------------------------------------------------------------------------------------ -- DECLARE VARIABLES FOR OUR DISPLAY.NEWTEXT() ------------------------------------------------------------------------------------ local string = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." local font = native.systemFont local fontSize = 20 local width = screenWidth ------------------------------------------------------------------------------------ -- PRE-CALCULATE NUMBER OF ROWS & PIXEL HEIGHT ------------------------------------------------------------------------------------ local space = textSpace.get(string, font, fontSize, width) print("NUMBER OF ROWS: "..space.numRows) print("HEIGHT IN PIXELS: "..space.pixelHeight) ------------------------------------------------------------------------------------ -- CREATE AN ACTUAL DISPLAY.NEWTEXT() AND SEE IF WE GOT THE RIGHT NUMBER... ------------------------------------------------------------------------------------ test = display.newText(string, centerX, centerY, width, 0, font, fontSize) print("actual pixelHeight on-screen: "..test.height)

Hope this helps!

I think you can condense it by using the table options for display.newText. Also, I found I had to floor the division to get the correct number of lines. I tried with the system font and a custom one.

[lua]

– options is a table with the fields: text, align, x, y, font, fontSize, width, parent

function getTextRowsHeight(options)

  local txt=options.text

  options.text=" "

  local tempRow = display.newText(options)

  local rowHeight = tempRow.height

  

  options.text=txt

  local tempText = display.newText(options)

  local h=tempText.height  

  local numRows = math.floor(h/rowHeight)

  

  tempText:removeSelf()

  tempRow:removeSelf()

  – return a table with the number of rows & pixel height

  return {numRows=numRows, height=h}

end
[/lua]

tap32’s function works and should be the preferred answer - but even simpler is to not specify a height and use it later to move the text in place (and increase some y position for any stuff that should be below or around the text box). Mark as Solved!