Looking for feedback/suggestions on my free bitmap font library (bit like TextCandy ...)

This is quite awesome!

Great work.

I’d really like to see that. It’s certainly do-able on what you describe.

Now supports font tinting, partially. You can tint the whole thing, or using the modifiers, but there is no easy syntax for it - considering something like “Hello {red}World {1,0,1}more” sort of syntax for colouring individual letters easily.

Picture shows multi line, UTF-8 (character after ‘curve’), word effects, tinting. Not the animation obviously :slight_smile:

snap2.png

This keeps getting better and better!

Thanks for your continued efforts on this. You have saved me from reinventing the wheel for a game i am working on.

Btw, i’m sure a lot of people would be willing to pay for this, when you deem it finished.

Keep up the great work :slight_smile:

If you are ever in Ireland, i’ll have to buy you a pint of Guinness :wink:

The standard Corona way to handle dynamic scale naming is to have a base-name and let the imageSuffix in config.lua decide which suffix to use. Instead of having a forced naming convention, I’ve modified the code somewhat so that it uses the imageSuffix extensions found in config.lua.

First I require(“config”) at the top of the module to get the imageSuffix table.

In the code below, I’ve made 3 modifications delimited by:

–>> modification (n) >>>>>>>>>>>>>>>

some code here

–<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

I’ve done some initial testing and it seems to work well.

require ("config") . . . function BitmapFont:loadFont(fontName) local options = { frames = {} } local spriteCount = 1 local imageFile = nil local charData = {} local source = io.lines(self:getFontFile(fontName)) self.padding = { 0,0,0,0 } for l in source do --\>\>\> modification 1 \>\>\>\>\>\>\>\>\>\>\>\>\>\>\>\>\>\>\>\> local page = l:match('^%s\*page%s\*id%s\*=%s\*(%d+)%s\*file%s\*=%s\*%"(.\*)%"$') local fileName = fontName .. self.suffix .. ".png" --\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\< if page ~= nil then assert(page == "0","We do not support font files with page \> 0. If you have one contact the author") imageFile = BitmapFont.fontDirectory .. "/" .. fileName end if l:match("^%s\*char%sid") ~= nil then local x,y,w,h = l:match("x%s\*=%s\*(%d+)%s\*y%s\*=%s\*(%d+)%s\*width%s\*=%s\*(%d+)%s\*height%s\*=%s\*(%d+)%s\*") assert(h ~= nil,"Failure to read line in .fnt file, contact author") local optionsEntry = { x = x\*1, y = y\*1, width = w\*1, height = h\*1 } options.frames[spriteCount] = optionsEntry local charID,xOffset,yOffset,xAdvance = l:match("id%s\*=%s\*(%d+).\*xoffset%s\*=%s\*([%-%d]+).\*yoffset%s\*=%s\*([%-%d]+).\*xadvance%s\*=%s\*([%-%d]+)") assert(xAdvance ~= nil,"Failure to read line in .fnt file, contact author") local charInfo = { width = xAdvance\*1, xOffset = xOffset\*1, yOffset = yOffset\*1} charInfo.spriteID = spriteCount charInfo.frame = optionsEntry charInfo.height = optionsEntry.height assert(charData[charID] == nil,"Duplicate character code, contact author") charData[charID\*1] = charInfo spriteCount = spriteCount + 1 end if l:match("padding") ~= nil then self.padding[1],self.padding[2],self.padding[3],self.padding[4] = l:match("padding%s\*%=%s\*([%d+])%s\*%,%s\*([%d+])%s\*%,%s\*([%d+])%s\*%,%s\*([%d+])") assert(self.padding[4] ~= nil,"Bad padding parameter format") for i = 1,4 do self.padding[i] = self.padding[i] \* 1 end end end assert(imageFile ~= nil,"No image file in fnt file, contact the author") self.imageSheet = graphics.newImageSheet(imageFile,options) assert(self.imageSheet ~= nil,"Image file " .. imageFile .. "failed to load for fnt file ".. fontName) return charData end --//% Get font file name, scaling for display size and checking for @nx files. --// @fontFile [string] Base name of font file. May use @4x tags to force high res font --// @return [string] Path of font file. function BitmapFont:getFontFile(fontFile) --\>\> modification 2 \>\>\>\>\>\>\>\>\>\>\>\>\>\>\>\>\>\>\>\>\>\> --// TODO: Work out scale, check if 4x, 3x 2x 1x exist and use them if they do. --// TODO: Explain in docs what it actually does. -- local scaleFactor = 1 / display.contentScaleX -- if fontFile:match("%@%d+x$") == nil then -- local deviceWidth = ( display.contentWidth - (display.screenOriginX \* 2) ) / -- display.contentScaleX -- scaleFactor = math.floor( deviceWidth / display.contentWidth ) -- scaleFactor = math.max(scaleFactor,1) -- while scaleFactor \> 1 and self:getFileNameScalar(fontFile,scaleFactor) == nil do -- scaleFactor = scaleFactor - 1 -- end -- end self.fontScalar = 1 / display.contentScaleX --\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\< return self:getFileNameScalar(fontFile,scaleFactor) end --//% Create a full file name for a font scaled by a particular amount (so 2 =\> @2x etc.) --// @fontFile [string] Base name of font file --// @fontScalar [number] Possible scalar size 2,3,4 etc. --// @return [string] Path of font file. function BitmapFont:getFileNameScalar(fontFile,fontScalar) --\>\> modification 3 \>\>\>\>\>\>\>\>\>\>\>\>\>\>\>\>\>\>\>\>\>\>\> local suffix = "" local selectedScale = -1 for k, v in pairs(application.content.imageSuffix) do if (self.fontScalar \>= v) and (v \> selectedScale) then selectedScale = v suffix = k end end fontFile = fontFile .. suffix self.suffix = suffix --\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\<\< return system.pathForFile(BitmapFont.fontDirectory .. "/" .. fontFile .. ".fnt", system.ResourceDirectory) end

Thanks - it’s much appreciated - do you want to push that or shall I do it ?

Tried out the code briefly this morning. The code itself is very impressive; the OO approach kind of makes my head spin, but clearly it works.

  1. You say the fonts directory is where the lua should go, but that’s not actually the case. Only the png and fnt files should go there.

  2. Near display.newBitmapText() you claim it’s not multiline compatible.

  3. You say that .x/y cannot be set after the fact? Are you not grouping the font characters into a display.newGroup()?

Yes (the tinting inline now works as well). Thanks for those. They are hangovers - originally I converted the .fnt to a file so I can require it, but now it just scans the .font file, it wasn’t multiline compatible originally.

The .x,.y can be set for the view group. 

It is a bit confusing. This is partly deliberate - I wanted to be able to have something like the display.newText() call because that’s how Corona is designed, so the display.newBitmapText is a wrapper.

The problem with that  is, what it returns isn’t any sort of display object - hence there is the getView() call - all the transitions refer to getView(). It can’t be treated like a displayObject, you can insert it into groups and things but you have to be careful.

It tends to have two of everything because of this. It’s a good idea to move it either with transition.to or setting x,y on the viewGroup, which works as normal, or to use the :moveTo() classes which also works, but if you try to do both it can get confusing as one ends up offsetting the other.

The driver is actually the singleton FontManager class, which updates the strings and does the animations,  this maintains a list of the strings and updates them if necessary. 

It is not best to use it in complex code but to use the strings as individual objects in a scene - this isn’t really a problem, because it would normally be used for title screens or scores (this is why it does vertical text as well), and use the clear call to clean up (or remove() them manually).

Because it’s not a proper display object it can’t clear itself up with string:getView():removeSelf() - it doesn’t work, it will leave the string behind because there is a lot of support stuff in there besides the display objects. Hence it has its own string:remove() method which clears everything up. It can’t be integrated in quite the same way as Corona Objects can be.

I don’t think it’s practical (unfortunately) to implement it as a pure Corona extension, it is doable but it would be extremely vulnerable to changes in the way Corona works.

The OO doesn’t really matter - it’s much the same as Corona in many ways. I do like chaining stuff which Corona doesn’t support but it’s just shorthand for multiple calls

instead of

x:setFillColor(0,1,1) x.setStrokecolor(1,0,1)

you just have

x:setFillColor(0,1,1):setStrokeColor(1,0,1)

which is neater :slight_smile:

very nice, i’ve actually been waiting (aka hoping) for someone else to come along and update the bmfont loader code. :smiley:
(i know bmGlyph has updated their support code, but i don’t necessarily need their whole product)

my two cents, for what it’s worth, would be a request to begin now (before it’s too late) to decouple the “base” font support from all the various “effects” and “formatting” stuff. make some mechanism for “extending” or “plugging into” the base with effects/formatting/etc modules if/as required.

one thing this’ll ‘force’ you to do is make sure all the necessary properties in base are adequately exposed, which also will make it easier for end-user to plug in their own variants. for example, most of your effects i could do without, but i’d like to know i could plug in my own (without any dependence on how YOU would do it) because all the right ‘access points’ are available, as i don’t necessarily need ‘yet another’ sinus scrolly written by a third party:   :wink:

[media]https://www.youtube.com/watch?v=_hyt0Cni9rQ[/media]

so base would make available methods to get individual chars, words, lines, etc; formatter module would handle assembling them as desired (multi-line? vertical stack? following a curve? etc); effects module would handle frame loops, registering ‘tweens’ and other modifiers, and such.

that’s probably asking for a big architectural change, but i’m just throwing it out there for your consideration, as i think the most important part is having a rock solid (and clean/uncluttered) base, making everything else (the ‘candy’ portion) optional.

fwiw, cheers

That’s not far off what happens. It’s designed to be trivially easy to use if you just want a sine text but fairly extensible.

(Incidentally, it is MIT license, open source on github, so it’s not dependent on me at all, though obviously I know best how it works !)

Basically it renders it as straight boring square text aligned neatly - this arrangement goes to drive the positioning stuff, irrespective of where the fonts actually end up.

Everything else is done using modifiers, which can be ignored if you just want straight text. These apply after the effect modifications to the bitmaps, however you like, individual characters, words, lines, over the whole thing, whatever pretty much you want.

So supposing you wanted the third word to be twice the size your modifier and rotated 45 degrees (the characters, not the whole thing) would be something like.

if info.wordIndex == 3 then modifier.xScale,modifier.yScale = 2 modifier.rotation = 45 end&nbsp;

The built in ones all use that system, they are all about 2 or 3 lines of code each (there is a helper function which calculates bits of curves). These only affect how the text looks - where the bitmaps end up and how they are rendered, rather than how anything else actually behaves (except if you use an event listener, tap/touch are dependent on where the bitmaps actually are).

If you have a look at the main github archive, the demo flashes the letters of the word ‘pulse’ individually in sequence. This is the code for the modifier (can be a class instance or function) which does that.

(info.elapsed is the time since first display, info.index the current character number, info.length the current line length)

function pulser(modifier, cPos, info) local w = math.floor(info.elapsed/360) % info.length + 1 if info.index == w then local newScale = 1 + (info.elapsed % 360) / 360 modifier.xScale,modifier.yScale = newScale,newScale modifier.tint.red = 0 end end

Your effect would be a little more complicated than the standard curve, because the curve scale changes over time - probably an extra line or two :slight_smile:

I don’t really understand *why* your bitmap text objects can’t be treated like regular display objects (or groups)? I mean, you still have to bring the frames in using display.newSprite and the sheet in with graphics.newImageSheet. Not to question your approach, just honestly unaware of the strategy behind it. :slight_smile:

They can be up to a point. It should work as normal.

What doesn’t happen , at the moment, is the clearing up bit. If you access the view group it should work like any other Corona display object, but the object itself isn’t a display object, it’s a class which manipulates display objects with a common group.

I have some ideas about how to improve the situation so it can respond appropriately if it is removed by (say) being part of a group which is removed. According to the SDK docs it sets the view groups metatable to nil if it is removed, so it should be possible in that case to detect that and remove the object references and so on. 

I will give it some thought.

L8R: If you create a text string and do str:getView():removeSelf() it crashes, which isn’t a surprise, but something I need to fix.

I’m still testing it to see if there are any unwanted side-effects to my changes.

Did you see if my changes have broken something in your object logic or not?

I can do the push when I’m done. Do you want me to fork it and create a separate branch with a pull request?

Okay that was probably not a sensible thing to do, but I am concerned at composer’s garbage collection (which doesn’t seem to work properly) and also the display.remove() which may be related. It would appear that nesting groups doesn’t clean up properly - the bitmapstring is a group of bitmaps, which in Composer ends up inserted into the scenes own group, if it garbage collects it apparently doesn’t do anything to the group or the contents… am awaiting some clarification on this problem.

For those that are using this library, opengameart.com has a great collection of bitmap fonts that would work well.

http://opengameart.org/content/bitmap-font-pack

Do you know if this is a common format for bitmap fonts ? Could do it one of two ways ; could modify the library to read them (it is expecting FNT/PNG files as in Glyph Designer) or simply write code to generate the FNT file to match ? 

@paul, I haven’t used your library (definitely going to; it looks superb) but I figured those fonts would work out well. The .zip file does have all fonts as PNG files. Should I be looking for a specific bit sizes? Let me know and I’ll modify my post. Thanks!

No, I’ll have a tinker, probably Tuesday sometime, busy day tomorrow :slight_smile: and come up with some sort of easy solution for converting these font files into bitmap fonts. Not difficult, but the problem is they are all different layouts and so on, and the font manager basically needs to know where the characters for each font are. Most of those look to be straight ASCII and either horizontal or vertical fixed sizes, but there are other bitmap fonts in OpenClipArt which are slightly different.

Just glancing at the font files, the only “easy” way to map them (since they lack .tbl or .fon) is to:

  1. calculate if it’s a horizontal or vertical font using file extents

  2. use a manual arg to determine character size (there are a bunch of non-square ones in there)

  3. Assume it’s a direct key map and write a sequence mapper (00=A, 1A=a, and so on)

To be honest though some of the fonts clearly don’t follow a standard sequence. The safest, most reliable way to accept these fonts is either insist on .fon/.tbl or insist on a string map argument (ex: loadFont(file.png, “ABCDEFabcdef2421”)) That way, character 1 would be A, character 7 would be ‘a’, and so on…)

Looks like I got the waters all muddy… sorry about that folks. I have a tendency to leap before I look!