A little while ago there was a tool called GlyphDesigner released by 71squared. The app is for the mac and allows you to design cool effects for fonts you have on your computer. The tool is really good and I wonder you can add support for that tool so corona reads font files created by GlyphDesigner?
Awesome, thanks.
I made arial32.fnt file and a .png in GlyphDesigner and tried it with your code but when I ran it in the simulator I got this error;
Runtime error
...1HGkKXNH+WWf6ydHU3uW++++TI/TemporaryItems/48/bmf.lua:71: ERROR: Invalid parameter passed to sprite.newSpriteSheetFromData(). Missing data.
stack traceback:
[C]: ?
[C]: in function
'newSpriteSheetFromData'
...1HGkKXNH+WWf6ydHU3uW++++TI/TemporaryItems/48/bmf.lua:71: in function 'loadFont'
...HGkKXNH+WWf6ydHU3uW++++TI/TemporaryItems/48/main.lua:13: in main chunk
Runtime error: ...1HGkKXNH+WWf6ydHU3uW++++TI/TemporaryItems/48/bmf.lua:71: ERROR: Invalid parameter passed to sprit
e.newSpriteSheetFromData(). Missing data.
stack traceback:
[C]: ?
[C]: in function 'newSpriteSheetFromData'
...1HGkKXNH+WWf6ydHU3uW++++TI/TemporaryItems/48/bmf.lua:71: in function 'loadFont'
...HG
kKXNH+WWf6ydHU3uW++++TI/TemporaryItems/48/main.lua:13: in main chunk
Do you have any ideas why this happen? I have the .fnt file and the .png in the same directory as the main.lua and the bmf.lua
I just copied your code and made the bmf.lua file and copied the other code into my main.lua. Do I have to make a spriteSheet out of my arial.png too? Do I have to slice the all the letters into individual sprites to make a arial32.lua spriteSheet?
How did you make it work?
David
Here is a link to my example project, including font file:
http://ember.awirtz.com/bmf.zip
If you are having trouble, you can send me your sample project and I can take a look and see if I can figure out what is being buggy...
I got it to work, but I had a strange problem though. I copied everything of your code exactly like in your file you posted for download and when I ran the project I got the error above, but here is the strange part...
when I made a new set of files and replaced the old ones, the project worked???
I tried the old files 20 times and they failed in simulator but when I copied the code and pasted them into new file it built fine, is that a bug in corona or something?
I wish a clean Target/All Targets option were built in Corona Simulator, just like in xcode.
Are you perhaps using Windows?
(I wouldn't assume so if you are talking about Glyph Designer, which is a Mac program)
If so, maybe it's a Windows vs Mac end-of-line marker issue?
I tried freshly downloading and running the zip on my Mac and it doesn't have issues.
I got a mac, I've been testing some more and it seems to be something wrong with the font I used. I made a few different fonts and there was only that one that didn't work (materhorn.fnt, 64pt). I only changed the fonts and the rest was the standard presets that starts with glyph designer.
I have no clue what the problem could be? I can sent you the font file.
David
Thanks for sending the file.
After trial and error removing various lines from the FNT file until I isolated the problem, I discovered that the "`" (backtick) character for this font has a negative y-offset.
This confuses some of the math which calculates the spritesheet coordinates.
Also, I noticed that this FNT file has quite a few kernings, which my code currently ignores.
I'm working on updating the code to handle the negative y-offset and adding support for kernings.
In the mean time, if you delete the line for backtick (id=96) from the FNT file, you should be able to start working with your Materhorn.FNT while you are waiting for me.
I've updated my code to support negative offsets, added kerning support, and added support for newlines.
The new code is available at the link above. I also included an Impact-based font rather than the old Futura-based one to demonstrate the kerning support. (Notice that the "W"s and "A"s interlock properly)
One other caveat that I have discovered (technically it could be considered a bug in Glyph Designer) is that if you set the spacing too low (e.g. 0), some of the character x/y coordinates end up negative. Just make sure you have spacing set to at least 1px.
Cool, what does the dumper.lua file do?
Do I have to include that file in my projects when i use bmf?
No, you don't. Sorry I didn't mean to include that in the zip. (removed it now)
It is a Lua library for dumping the contents of data structures to the console so I can see what's going on inside of them when debugging.
@p120ph37:
thanks for the bmf.lua file!
i got a glyph designer license now and i am using the bmf.lua file to render a big bitmap font style timer that is updated by a Runtime "enterframe" event handler.
it is a central element of my game and it looks amazing.
--> thanks so far!
the only problem i face now is that when i check for memory leaks with instruments, it seems that the graphics that are created are not deallocated and i get memory warnings/crashes after a while.
i experimented with 'object:removeSelf()' and set the variables in my code to nil, it didn't help.
this is what it looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | -- SHOW TIME PLAYED WITH BMP CUSTOM FONT --initialize bmp font timer: local Timestring = bmf.newString(font2, "00:00:00:00") Timestring.x = 55 Timestring.y = 70 --update bmp font timer: function TimestringUpdate (newString) Timestring:removeSelf() Timestring = nil Timestring = bmf.newString(font2, newString) Timestring.x = 55 Timestring.y = 70 newString=nil end --newString parameter comes from another function -- this is called by a runtime "enterframe"event listener |
now i think maybe it is happening inside the bmf.lua file?
when you return the font object in the newString function, is there a way to clean up the memory after it returned the object?
Big Thanks!
I edited the code a little so it only draws a new picture every 50ms.
helps a little because now it uses up the memory much slower, but doesn't solve the problem.
Thanks for the feedback - So far I have used it mostly for fairly low-refresh things so I hadn't seen the memory leak issues. I will look into it when I get a chance. Can you provide me with the debug statements you are using to track the memory usage too?
In the mean time, I have some minor updates of my own that I've made to the BMF library while working on my own projects, which I have uploaded to the ZIP. I fixed an off-by-one issue with spritesheet alignment, added support for newlines, and added placeholder rectangles so that the text centers correctly when there are leading/trailing spaces or blank lines.
Actually, I think I've located the issue. ( I took your example code and fed it system.getTimer() data every frame while monitoring the value of collectgarbage( 'count' ) )
It seems that Corona does not automatically free the memory used by children of a group object when the group object is removed.
As a work-around, I have overridden the 'removeSelf' method of my string objects (really just group objects under the hood) so that it removes its children before removing itself.
Your memory leak should now be gone with the latest version of the library.
Cheers!
NB: If you use the parent:remove( string ) form rather than string:removeSelf(), this will not hit my custom removeSelf method.
Wow, Great News! I will try it out Tomorrow!
ahh.. download link seems to be broken. could you check?
works like a charm:
thank you!
I threw a few more examples and sample fonts into the demo - this should give people something decent to play with before deciding if they want to buy Glyph Designer.

This is great, very much help. As an owner of Glyph Designer who likes the look of Text Candy how can I easily convert angelcode .fnt into .lua spritesheet?
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... :-D
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.
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.
1 2 3 4 5 | module( ... ) require( 'bmf' ) function getSpriteSheetData() return bmf.loadFont( _M._NAME ).spritesheets[0] end |
Let me know if that works out for you - I haven't actually tested it yet.
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.
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.
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.
Thanks very much for all your help with this, I still have a tonne to learn but hopefully I will get there. :)
You can Download Computer Fonts which you required and upload in your system.
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:
1 | textureRect = { x = 0 + t.x, y = 0 + t.y, width = -1 + t.width, height = t.height }, -- removed "-1" for "height" |
@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... ;-)
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.
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.
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:
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 74 75 76 77 78 79 | 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 |
@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!
Oh, I see :) 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:
1 2 3 | abcdefg abcde abc |
1 2 3 | abcdefg abcde abc |
@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:
Soon I'll be adding:
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 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 | 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 |
Ack, Line 246 was full of silliness, if you wanted random purple tinting leave it alone, otherwise it should read:
t[i][j]:setFillColor(v[1],v[2],v[3])
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 | -- 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.
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?
@Sun.Jiajie hudText:dispose()
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 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | 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 |
hi @ vitalyx
i even updated the font:dispose() function , but the mem didn't have any change, really wierd
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 | 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 |
hi @Sun.Jiajie Sorry, can't confirm.
Just spend some time switching scenes in my game (from one that utilizes a bitmap font to one that don't and back). Here's the result:

Hi everyone,
i recently released a new bitmap font generator for OSX: bmGlyph (http://www.bmglyph.com).
the export format is compatible with the bmf project posted above.
however, a bmGlyph user reported some difficulties using HD and SD font format (which is automatically generated by bmGyph), and after looking at it, i made a quick fix to the code.
this will work only for iPhone/iPhone4 (actually it depends on the scale factor), but it should be pretty easy to adpat it to other scale factor.
here is what i do:
1 2 3 4 5 6 7 8 | local bmglyphfnt if display.contentScaleX == 0.5 then bmglyphfnt = bmf.loadFont( 'testbmglyph-hd.fnt' ) else bmglyphfnt = bmf.loadFont( 'testbmglyph.fnt' ) end local string = bmf.newString(bmglyphfnt, "Loading ... ") |
in bmf.lua, i changed the set_text function ad follow:
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 | 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 if display.contentScaleX == .5 then t.raw_font.info.lineHeight = t.raw_font.info.lineHeight /2 end 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 if display.contentScaleX == .5 then --scale the highres spritesheet if you're on a retina device. letter.xScale = .5 letter.yScale = .5 letter.x = t.raw_font.chars[ c ].xoffset/2 + x letter.y = t.raw_font.chars[ c ].yoffset/2 - t.raw_font.info.base + y else letter.x = t.raw_font.chars[ c ].xoffset + x letter.y = t.raw_font.chars[ c ].yoffset - t.raw_font.info.base + y end t:insert( letter ) last = c end if display.contentScaleX == .5 then x = x + t.raw_font.chars[ c ].xadvance/2 else x = x + t.raw_font.chars[ c ].xadvance end if x >= xMax then xMax = x end end end local background = display.newRect( 0, -t.raw_font.info.base, xMax, yMax ) obj:insert( background ) background:setFillColor( 0, 0, 0, 0 ) end t.align = oldAlign end |
the text displayed is now using the HD texture on iPhone4 and SD on iPhone.
i'm not sure it is the right way to deal with hd and sd textures on Corona, feel free to correct it. but at least it works :)
hope that helps!
Congrats, squeraud! Looks like an awesome app :) Too bad we don't have something like this for Windows. Sure, we have BMFont, but it is more basic. Will be using your app in a virtual machine.
Hi, I am having a small group that contains 1 background image (Square) and one bitMap font.
The problem is that I have no clue how I should center the bitMap above my image? I was trying to do it manually but the text doesn't align perfect in that way.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | level[i] = display.newGroup() level[i]:insert(display.newImage( "level.png" )) local bmglyphfnt if display.contentScaleX == 0.5 then bmglyphfnt = bmf.loadFont( 'hobo-hd.fnt' ) else bmglyphfnt = bmf.loadFont( 'hobo.fnt' ) end local string = bmf.newString(bmglyphfnt, i) string.x = 10; string.y = 80 level[i]:insert(string) |
Kind regards, Joakim
Try this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | -- Only need to do this once local bmglyphfnt if display.contentScaleX == 0.5 then bmglyphfnt = bmf.loadFont( 'hobo-hd.fnt' ) else bmglyphfnt = bmf.loadFont( 'hobo.fnt' ) end local level = {} for i=1,NUM_LEVELS do level[i] = display.newGroup() level[i]:insert(display.newImage( "level.png" )) local string = bmf.newString( bmglyphfnt, string.format("%d", i) ) level[i]:insert(string) string:setReferencePoint( display.TopLeftReferencePoint ) string.x = 0.5*( level[i].contentWidth - string.contentWidth ) string.y = 0.5*( level[i].contentHeight - string.contentHeight ) end |
Just to add my thought : TextCandy does the job perfectly well with no mem troubles, and supports glyph designer.
hi,
you will probably need to get the baseline of the font (assuming that you want to center only numbers which or not going under the baseline of the font)
check in the fnt file, at the top, the lineheight and baseline values. the non-inverted baseline value will be 'lineheight - baseline'. now you should only have to get the center of your level.png and shift the string position like this:
1 2 3 | ... string.x = level.width/2 - string.width/2 string.y = level.height/2 + string.height/2 - baseline |
Thanks, @KenRogoway!
That did the stuff and it was not so hard to understand!
Joakim
Hi again,
I want to keep my things in places so it looks tidy in my main directory.
But I can't place my font files in a folder? Any suggestions?
Regards, Joakim
You CAN place your fonts in a folder. I do. When you call the function to add the font just do something like this:
1 2 3 4 5 | FONT_DIR = "Fonts/" Calibri48BoldBlack = bmf.loadFont( 'CalibriBold_Black48.fnt', FONT_DIR ) bmf.newString( Calibri48BoldBlack, "Text Message" ) |
Great that seems straight forward, but I am getting "Cannot create path for resource file 'hobo-hd.fnt'. File does not exist" error. The fonts are in a folder "fonts" in the root directory of the project.
1 2 3 4 5 6 7 8 9 10 | local bmglyphfnt local FONT_DIR = "fonts/" if display.contentScaleX == 0.5 then bmglyphfnt = bmf.loadFont( 'hobo-hd.fnt',FONT_DIR ) bmglyphfnt1 = bmf.loadFont( 'badaboom-hd.fnt',FONT_DIR ) else bmglyphfnt = bmf.loadFont( 'hobo.fnt',FONT_DIR ) bmglyphfnt1 = bmf.loadFont( 'badaboom.fnt',FONT_DIR ) end |
Any suggestions?
Joakim
Merry Christmas...
[edit]
Old code removed - see link below for latest code.
[/edit]
I don't have the registered version of Glyph Designer to test with, but it works with BMFont output, so it ought to work with Glyph Designer as well.
[edit]
After playing with Glyph Designer, I bought it and exported a font. I discovered and fixed an issue in my code that didn't like the fact that space was encoded as a 0px by 0px bitmap, and I can now say I have tested it with Glyph Designer output and it works nicely.
[/edit]