Bitmap font

I don’t own Text Candy, and there doesn’t seem to be any detailed information on the website describing the spritesheet definition format that they are using, so I don’t know for sure, but I imagine it shouldn’t be terribly difficult to take my .fnt parsing routine and modify it slightly to work as a Text Candy .lua file.

Incidentally, I have some new code I’ve been working on for my BMF library that allows you to use bitmap fonts for text input fields and to change the text or font of a label without having to destroy and recreate it entirely. Stay tuned… :smiley: [import]uid: 32962 topic_id: 6060 reply_id: 28687[/import]

Thanks for the speedy response and very much looking forward to BMF developments:)

It looks like Candy are using the the standard Corona table .lua spritesheet definition as produced by Zwoptex/Texturepacker.

[import]uid: 6086 topic_id: 6060 reply_id: 28771[/import]

Thanks for the info.

I looked at how Zwoptex structures its .lua file and it’s pretty straightforward.
So to use AngelCode-style .fnt files as font definitions for TextCandy, you should be able to just save this code snippet with the same base name as your myfont.png and myfont.fnt files (e.g. myfont.lua) and use that as your TextCandy definition file.

[lua]module( … )
require( ‘bmf’ )
function getSpriteSheetData()
return bmf.loadFont( _M._NAME ).spritesheets[0]
end[/lua]

Let me know if that works out for you - I haven’t actually tested it yet. [import]uid: 32962 topic_id: 6060 reply_id: 28774[/import]

Thanks again http://developer.anscamobile.com/forum/2011/03/19/new-candy-tool-text-candy-corona-50-coupon-inside#comment-28723 is posted as a tutorial for creating fonts
I haven’t bought Candy yet as I was hoping to purchase both products together:) but I guess I will just have to take the plungette now and pay a pal!

Your BMF lib takes kerning into account? I don’t think text Candy does. [import]uid: 6086 topic_id: 6060 reply_id: 28781[/import]

Right - from the settings I they describe in the documentation, it doesn’t seem that TextCandy handles kerning or any sort of character overlap for that matter.

Also, my example probably won’t work as-is… I forgot that in the version of my BMF library that’s on the server right now I’m destroying the spritesheet definition once the spritesheet is loaded so it’s no longer accessible after loadFont has completed.

Plus you probably want to get additional information from the .fnt file besides just the sprite locations - like the line height and the space width and y-offsets. [import]uid: 32962 topic_id: 6060 reply_id: 28783[/import]

I’ve updated the library so it doesn’t destroy the spritesheet definition.

I’ve also included a sneak-peek at a few new features that I haven’t had a chance to show off in the demo yet:

object.text and object.font are now properties which can be read/written after the object is created.
object.align allows some rudimentary text alignment options.
object.input() allows you to use a text object as an input field - no more ugly white input boxes! (this is still a bit in development - expect more detailed documentation soon…)

Finally, I’ve posted to the TextCandy thread with some ideas on integration - we’ll see if X-Pressive wants to help with that. [import]uid: 32962 topic_id: 6060 reply_id: 28795[/import]

Thanks very much for all your help with this, I still have a tonne to learn but hopefully I will get there. :slight_smile: [import]uid: 6086 topic_id: 6060 reply_id: 29083[/import]

You can Download Computer Fonts which you required and upload in your system.
[import]uid: 71156 topic_id: 6060 reply_id: 41865[/import]

p120ph37, thanks for sharing! But do you have any updates for this? When navigating away from the scene that uses BMF fonts the simulator hangs for me with 100% CPU. I’m using the Director class to manage scenes, so maybe this has something to do with Director’s cleangroups() method.
Also, I noticed there is 1px clipping at the bottom with the font I generated with BMFFont.

EDIT: I found the reason for trimming, changed code on line 40 to:
[lua]textureRect = { x = 0 + t.x, y = 0 + t.y, width = -1 + t.width, height = t.height }, – removed “-1” for “height”[/lua]
But the simulator still hangs. Will keep looking. [import]uid: 52103 topic_id: 6060 reply_id: 46083[/import]

@vitalyx

Hi,
Sorry I haven’t updated the code in a while. One of the tricks in my BMF implementation was that I had to clean out the contents of the group objects as they were destroyed since Corona at the time I wrote the code had a bug which would leak memory if I didn’t. I understand that has been fixed in the more recent builds, but I haven’t had a chance to try it out yet. You might try removing that part of my code and see if it helps your issue. As far as coordinates and the 1px clipping, I did most of my testing with the fonts from Glyph Designer, so it may be possible that BMFFont uses slightly different coordinate numbering - I’ll have to look into that at some point, but if you have it working for your particular font, great! If you do decide that BMF fonts are a good option in Corona and if you have a Mac available, I would highly recommend trying out Glyph Designer… :wink:

I can’t promise anything on timeframes, but I’ll try to get back to this library sometime soon to update it and release a couple of extras I’ve been working on for it. [import]uid: 32962 topic_id: 6060 reply_id: 46106[/import]

Hi p120ph37, thanks for your reply. I will be looking forward to this update. Till then I will probably put up a tiny module of my own, since my only need for now is displaying outlined numbers, so no worries. [import]uid: 52103 topic_id: 6060 reply_id: 46114[/import]

I decided to dive into your code instead and understand what it does.
Pretty clever, but quite hard to read, IMO.
I’ve modified the newString() function.
Now there is no need for accessorize/removerize functions, behavior looks the same (though, I haven’t done much testing), there is less code and it’s easier to understand.

Here it is:
[lua]function newString(sFont, sText)
local obj = display.newGroup()

local font, text, align

function obj:getFont()
return font
end

function obj:getText()
return text
end

function obj:getAlign()
return align
end

function obj:setAlign(a)
align = a
local width = self.contentWidth
if a == “right” then
for i = 1, self.numChildren do
self[i].x = self[i].x + width
end
elseif a == “center” then
for i = 1, self.numChildren do
self[i].x = self[i].x - math.floor(width * 0.5)
end
end
end

function obj:setText(t)
if type(t) ~= “string” then return end

text = t
for i = self.numChildren, 1, -1 do self[i].removeSelf() end
local x, y, xMax, yMax = 0, 0, 0, 0
local last = “”

for c in string.gmatch(text … “\n”, “(.)”) do
if c == “\n” then
x = 0
y = y + font.info.lineHeight
if y >= yMax then yMax = y end
elseif font.chars[c] then
if 0 + font.chars[c].width > 0 and 0 + font.chars[c].height > 0 then
local letter = sprite.newSprite(font.sprites[c])
letter:setReferencePoint(display.TopLeftReferencePoint)
if font.kernings[last … c] then
x = x + font.kernings[last … c]
end
letter.x = font.chars[c].xoffset + x
letter.y = font.chars[c].yoffset - font.info.base + y
self:insert(letter)
last = c
end
x = x + font.chars[c].xadvance
if x >= xMax then xMax = x end
end
end

local background = display.newRect(0, -font.info.base, xMax, yMax)
obj:insert(background)
background:setFillColor(0, 0, 0, 0)

self:setAlign(align)
end

function obj:setFont(f)
font = f
self:setText(text)
end

align = “left”
text = sText
obj:setFont(sFont)

return obj
end[/lua] [import]uid: 52103 topic_id: 6060 reply_id: 48134[/import]

@vitalyx:

That’s similar to what I had at first. I added the “accessor” functionality so that it would behave more like the native text strings where you can change the displayed text by just setting the value of the “text” attribute rather than by calling a method. The idea was to make it usable as a drop-in replacement for the native text objects. Removing that layer of abstraction will certainly make the code easier for you to understand and work on if you want to learn how it works though! [import]uid: 32962 topic_id: 6060 reply_id: 48168[/import]

Oh, I see :slight_smile: Well, maybe this will make it easier for somebody else who is curious what’s going on there. As I rarely just copy-paste other people’s code and usually try to at least understand it. But most of the time modify it to suit the needs of my project or just to make it conform with the rest of the code better.

BTW, the above code appears to work fine with the Director class, so if somebody had this same problem, this is worth noting.

Also, I haven’t figured out how alignment works.
I would expect “right” to look like this:

abcdefg  
 abcde  
 abc  

and “center” like this:

abcdefg  
 abcde  
 abc  

But it’s not the case. Not important for my particular project, but again, worth noting. [import]uid: 52103 topic_id: 6060 reply_id: 48181[/import]

@p12Oph37

I’ve added some new features I needed to this library, hopefully it’s helpful. Would you be game for adding this code to github so we can manage changes easier? I haven’t tested this fully yet, but it does seem to work for my project.

My changes:

  • Added a newParagraph function with word-wrapping

  • Added support for tinting paragraphs (requires build 614+)

  • Fixed display issue (spacing) for fonts that use the outline effect

  • Fixed issue that caused some fonts height/width to be off by one pixel

Soon I’ll be adding:

  • Support for defining a callback function for each word in a paragraph

  • The ability to tint a single word

[lua]module( …, package.seeall )

– AngelCode bitmap font support

require( ‘sprite’ )

– Specify an Angelcode format bitmap font definition file (".FNT")
– The spritesheet(s) that this file references need to be located in the resource directory.
– Return value is a font object that can be used when calling newString
function loadFont( fntFile )
local function extract( s, p )
return string.match( s, p ), string.gsub( s, p, ‘’, 1 )
end
local font = { info = {}, spritesheets = {}, sprites = {}, chars = {}, kernings = {} }
local readline = io.lines( system.pathForFile( fntFile, system.ResourceDirectory ) )
for line in readline do
local t = {}; local tag;
tag, line = extract( line, ‘^%s*([%a_]+)%s*’ )
while string.len( line ) > 0 do
local k, v
k, line = extract( line, ‘^([%a_]+)=’ )
if not k then break end
v, line = extract( line, ‘^"([^"]*)"%s*’ )
if not v then
v, line = extract( line, ‘^([^%s]*)%s*’ )
end
if not v then break end
t[k] = v
end
if tag == ‘info’ or tag == ‘common’ then
for k, v in pairs( t ) do font.info[k] = v end
elseif tag == ‘page’ then
font.spritesheets[1 + t.id] = { file = t.file, frames = {} }
elseif tag == ‘char’ then
t.letter = string.char( t.id )
font.chars[t.letter] = {}
for k, v in pairs( t ) do font.chars[t.letter][k] = v end
if 0 + font.chars[t.letter].width > 0 and 0 + font.chars[t.letter].height > 0 then
font.spritesheets[1 + t.page].frames[#font.spritesheets[ 1 + t.page].frames + 1 ] = {
–textureRect = { x = 0 + t.x, y = 0 + t.y, width = -1 + t.width, height = -1 + t.height }, --CLF removed the -1, it was causing issues with fonts with borders
textureRect = { x = 0 + t.x, y = 0 + t.y, width = t.width, height = t.height },
spriteSourceSize = { width = 0 + t.width, height = 0 + t.height },
spriteColorRect = { x = 0, y = 0, width = -1 + t.width, height = -1 + t.height },
spriteTrimmed = true
}
font.sprites[t.letter] = {
spritesheet = 1 + t.page,
frame = #font.spritesheets[1 + t.page].frames
}
end
elseif( tag == ‘kerning’ ) then
font.kernings[string.char( t.first ) … string.char( t.second )] = 0 + t.amount
end
end
for k, v in pairs( font.spritesheets ) do
font.spritesheets[k].sheet = sprite.newSpriteSheetFromData( v.file, v )
end
for k, v in pairs( font.sprites ) do
font.sprites[k] = sprite.newSpriteSet( font.spritesheets[v.spritesheet].sheet, v.frame, 1 )
end
return font
end

– extend an object with accessor behaviors
local function accessorize( t )
local mt = getmetatable( t )
setmetatable( t, {
__index = function( t, k )
if rawget( t, ‘get_’…k ) then
return rawget(t, ‘get_’…k )( t, k )
elseif rawget( t, ‘raw_’…k ) then
return rawget( t, ‘raw_’…k )
elseif mt.__index then
return mt.__index( t, k )
else
return nil
end
end,
__newindex = function( t, k, v )
if rawget( t, ‘set_’…k ) then
rawget( t, ‘set_’…k )( t, k, v )
elseif rawget( t, ‘raw_’…k ) then
rawset( t, ‘raw_’…k, v )
elseif mt.__newindex then
mt.__newindex( t, k, v )
else
rawset( t, ‘raw_’…k, v )
end
end,
} )
end

– extend an object with cascading removeSelf
local function removerize( t )
local old = t.removeSelf
t.removeSelf = function( o )
for i = o.numChildren, 1, -1 do o[i]:removeSelf() end
old( o )
end
end

– Pass a font object (obtained from loadFont) and a string to render
– Return value is a DisplayObject of the rendered string
– object.font can be read/modifed
– object.text can be read/modified
– object.align can be read/modified - left/right/center (multiline not yet fully supported for non-left)
– object.input( function(text), { filter = function(), max = 32 } )
– turns the object into a text input.
– the callback is hit when the user presses “return” or the field losed focus.
– this code is under development - more documentation will be added soon…
function newString( font, text )
local obj = display.newGroup()
accessorize( obj )
removerize( obj )
obj.set_font = function( t, k, v )
obj.raw_font = v
if t.text then t.text = t.text end
end
obj.set_align = function( t, k, v )
local w = t.contentWidth
if t.raw_align == ‘right’ then
for i = 1, t.numChildren do
t[i].x = t[i].x - w
end
elseif t.raw_align == ‘center’ then
for i = 1, t.numChildren do
t[i].x = t[i].x + math.floor( w * 0.5 )
end
end
t.raw_align = v
if t.raw_align == ‘right’ then
for i = 1, t.numChildren do
t[i].x = t[i].x + w
end
elseif t.raw_align == ‘center’ then
for i = 1, t.numChildren do
t[i].x = t[i].x - math.floor( w * 0.5 )
end
elseif t.raw_align ~= ‘left’ then
t.raw_align = ‘left’
end
end
obj.set_text = function( t, k, v )
t.raw_text = v
for i = t.numChildren, 1, -1 do t[i]:removeSelf() end
local oldAlign = ( t.align or ‘left’ )
t.align = ‘left’
local x = 0; local y = 0
local last = ‘’; local xMax = 0; local yMax = 0
if t.raw_font then
for c in string.gmatch( t.raw_text…’\n’, ‘(.)’ ) do
if c == ‘\n’ then
x = 0; y = y + t.raw_font.info.lineHeight
if y >= yMax then
yMax = y
end
elseif t.raw_font.chars[c] then
if 0 + t.raw_font.chars[c].width > 0 and 0 + t.raw_font.chars[c].height > 0 then
local letter = sprite.newSprite( t.raw_font.sprites[c] )
letter:setReferencePoint( display.TopLeftReferencePoint )
if t.raw_font.kernings[last … c] then
x = x + font.kernings[last … c]
end
letter.x = t.raw_font.chars[c].xoffset + x
letter.y = t.raw_font.chars[c].yoffset - t.raw_font.info.base + y
t:insert( letter )
last = c
end
–x = x + t.raw_font.chars[c].xadvance
x = x + t.raw_font.chars[c].xadvance + (t.raw_font.info.outline or 0) --CLF added support for outlines
if x >= xMax then
xMax = x
end
end
end
local background = display.newRect( 0, -t.raw_font.info.base, xMax, yMax )
background.isBackground = true --CLF Added to support tinting of font.
obj:insert( background )
background:setFillColor( 0, 0, 0, 0 )
end
t.align = oldAlign
end
obj.input = function( f, args )
– spawn the text field invisibly
local field
local function char()
– check if any character has been added or deleted
if field.text ~= ‘–’ then
if string.len( field.text ) < 2 then
– backspace was pressed
if string.len( obj.text ) > 0 then
obj.text = string.sub( obj.text, 1, -2 )
end
else
– some other key was pressed
obj.text = obj.text…string.sub( field.text, 3 )
end
field.text = ‘–’
if args.filter then
obj.text = string.sub( args.filter( obj.text ), 1, (args.max or 32) )
else
obj.text = string.sub( obj.text, 1, (args.max or 32) )
end
end
end
Runtime:addEventListener( ‘enterFrame’, char )
local function done( e )
if e.phase == ‘submitted’ or e.phase == ‘ended’ then
native.setKeyboardFocus( nil )
field:removeSelf()
Runtime:removeEventListener( ‘enterFrame’, char )
f( text )
end
end
field = native.newTextField( 0, 0, 240, 24, done )
field.text = ‘–’
field.isVisible = false
native.setKeyboardFocus( field )
end
obj.font = font
obj.align = ‘left’
obj.text = (text or ‘’)
return obj
end

function newParagraph(font, text, width)
local obj = display.newGroup()
accessorize( obj )
removerize( obj )
obj.set_font = function( t, k, v )
obj.raw_font = v
–if t.text then t.text = t.text end
end
obj.set_paragraphWidth = function( t, k, v )
obj.raw_paragraphWidth = v
–if t.text then t.text = t.text end
end
obj.set_tint = function( t, k, v )
obj.raw_tint = v
for i = t.numChildren, 1, -1 do
for j = t[i].numChildren, 1, -1 do
if(not t[i][j].isBackground) then
if v[4] then
t[i][j]:setFillColor(v[1],v[2],v[3],v[4])
else
t[i][j]:setFillColor(v[1]-i*2,math.random(1,255),v[3])
end
end
end
end
–if t.text then t.text = t.text end
end
obj.set_text = function(t, k, v)
–save the new raw text
t.raw_text = v

–remove all children
for i = t.numChildren, 1, -1 do t[i]:removeSelf() end

–determine the width of a space
local space = bmf.newString(font, " ")
local spaceWidth = space.raw_font.info.outline*-1 or 0
space:removeSelf()
space = nil

–Create our word-wrapped paragraph.
local spaceLeft = t.raw_paragraphWidth
local x, y = obj.x, obj.y

for word, spacer in string.gmatch(v, “([^%s%-]+)([%s%-]*)”) do
local w = bmf.newString(font, word…spacer)
print(w.width,“sw” , spaceWidth, “sl”, spaceLeft)
if(w.width + spaceWidth) > spaceLeft then
spaceLeft = t.raw_paragraphWidth - w.width - spaceWidth
y = y + w.raw_font.info.lineHeight
x = obj.x
w.x = x
else
w.x = x + t.raw_paragraphWidth - spaceLeft
spaceLeft = spaceLeft - w.width - spaceWidth
end

w.y = y

obj:insert(w)
end
end

obj.paragraphWidth = width or display.viewableContentWidth
obj.text = text
obj.font = font
obj.tint = {255,255,255}

return obj
end[/lua] [import]uid: 60707 topic_id: 6060 reply_id: 56614[/import]

Ack, Line 246 was full of silliness, if you wanted random purple tinting leave it alone, otherwise it should read:

[lua]t[i][j]:setFillColor(v[1],v[2],v[3])[/lua] [import]uid: 60707 topic_id: 6060 reply_id: 56615[/import]

First, a big thanks for this code. Very nice and appreciated.

Second, there appears to be an issue with alignment. If I use newString() with a short text string, then later change the string to be much longer, it doesn’t align properly. It aligns to the right even though the default is “left” in the bmf code. I have also tried “center”, etc for bmf alignment, but since this applies it EVERY TIME I call that, it just moves the text over more and more.

 -- This part works fine  
 local statusMsg = bmf.newString( ImpactBold24, "Message" )  
 myGroup:insert( statusMsg )  
 statusMsg:setReferencePoint( display.TopCenterReferencePoint )  
 statusMsg.x = display.contentCenterX  
 statusMsg.y = display.contentCenterY  
  
 -- This will set the text correctly, but it will no  
 -- longer be centered on the screen in the X direction  
 statusMsg.text = "Much Longer Message"  
 statusMsg:setReferencePoint( display.TopCenterReferencePoint )  
 statusMsg.x = display.contentCenterX  
 statusMsg.y = display.contentCenterY  

I can provide a simple text app if needed, but you can duplicate this easily enough by doing what I have done above.

Hopefully the bmf code is still being supported either by the original author or by Chris. [import]uid: 16734 topic_id: 6060 reply_id: 58672[/import]

Hi all,

anyone realized that the loaded font can’t be nil and will keep stay in the memory ?

my code is like this,
local hudText = bmf.loadFont( ‘hudText.fnt’ )

And i have tried to nil the handler, even its children in the hudText manually,
but the memory didn’t get freed, 15kb mem will be used all the time.

Anyone know a better way to unload the font?

[import]uid: 74676 topic_id: 6060 reply_id: 62799[/import]

@Sun.Jiajie hudText:dispose()

[lua]module(…, package.seeall)

local sprite = require “sprite”

function loadFont(fntFile)
local function extract(s, p)
return string.match(s, p), string.gsub(s, p, ‘’, 1)
end

local font = { info = {}, spritesheets = {}, sprites = {}, chars = {}, kernings = {} }

local readline = io.lines(system.pathForFile(fntFile, system.ResourceDirectory))
for line in readline do
local t = {}
local tag
tag, line = extract( line, ‘^%s*([%a_]+)%s*’ )
while string.len( line ) > 0 do
local k, v
k, line = extract( line, ‘^([%a_]+)=’ )
if not k then break end
v, line = extract( line, ‘^"([^"]*)"%s*’ )
if not v then
v, line = extract( line, ‘^([^%s]*)%s*’ )
end
if not v then break end
t[k] = v
end
if tag == “info” or tag == “common” then
for k, v in pairs(t) do font.info[k] = v end
elseif tag == “page” then
font.spritesheets[1 + t.id] = { file = t.file, frames = {} }
elseif tag == “char” then
t.letter = string.char( t.id )
font.chars[t.letter] = {}
for k, v in pairs( t ) do font.chars[t.letter][k] = v end
if 0 + font.chars[t.letter].width > 0 and 0 + font.chars[t.letter].height > 0 then
font.spritesheets[1 + t.page].frames[#font.spritesheets[ 1 + t.page].frames + 1 ] = {
textureRect = { x = 0 + t.x, y = 0 + t.y, width = t.width, height = t.height },
spriteSourceSize = { width = 0 + t.width, height = 0 + t.height },
spriteColorRect = { x = 0, y = 0, width = -1 + t.width, height = -1 + t.height },
spriteTrimmed = true
}
font.sprites[t.letter] = {
spritesheet = 1 + t.page,
frame = #font.spritesheets[1 + t.page].frames
}
end
elseif tag == “kerning” then
font.kernings[string.char( t.first ) … string.char( t.second )] = 0 + t.amount
end
end

for k, v in pairs( font.spritesheets ) do
font.spritesheets[k].sheet = sprite.newSpriteSheetFromData( v.file, v )
end
for k, v in pairs( font.sprites ) do
font.sprites[k] = sprite.newSpriteSet( font.spritesheets[v.spritesheet].sheet, v.frame, 1 )
end

function font:dispose()
for k in pairs( self.spritesheets ) do
self.spritesheets[k].sheet:dispose()
end
end

return font
end

function newString(sFont, sText)
local obj = display.newGroup()

local font, text

function obj:getFont()
return font
end

function obj:getText()
return text
end

function obj:setText(t)
if type(t) ~= “string” then return end

text = t
for i = self.numChildren, 1, -1 do self[i].removeSelf() end
local x, y = 0, 0
local last = “”

for c in string.gmatch(text … “\n”, “(.)”) do
if c == “\n” then
x = 0
y = y + font.info.lineHeight
elseif font.chars[c] then
if 0 + font.chars[c].width > 0 and 0 + font.chars[c].height > 0 then
local letter = sprite.newSprite(font.sprites[c])
letter:setReferencePoint(display.TopLeftReferencePoint)
if font.kernings[last … c] then
x = x + font.kernings[last … c]
end
letter.x = font.chars[c].xoffset + x
letter.y = font.chars[c].yoffset - font.info.base + y
self:insert(letter)
last = c
end
x = x + font.chars[c].xadvance
end
end
end

function obj:setFont(f)
font = f
self:setText(text)
end

text = sText
obj:setFont(sFont)

return obj
end[/lua] [import]uid: 52103 topic_id: 6060 reply_id: 62812[/import]

hi @ vitalyx

i even updated the font:dispose() function , but the mem didn’t have any change, really wierd

[code]
function font:dispose()
for k,v in pairs( self.spritesheets ) do
self.spritesheets[k].sheet:dispose()
self.spritesheets[k].sheet = nil
end

for k,v in pairs(self.info) do
k = nil
end

for k,v in pairs(self.chars) do
for i,u in pairs(self.chars[k]) do
self.chars[k][i] = nil
end

self.chars[k] = nil

end

for k,v in pairs(self.sprites) do
self.sprites[k] = nil
end

collectgarbage( “collect” )
end

[/code] [import]uid: 74676 topic_id: 6060 reply_id: 62830[/import]