Found a bug in FontMachine

BlitzMax Forums/BlitzMax Programming/Found a bug in FontMachine

GfK(Posted 2014) [#1]
It looks like TBitmapFont.GetTxtWidth() isn't working right. If there is a space at the end of a string, the width of the space is ignored.

Import blide.fontmachine

Global font:TBitmapFont = LoadBitmapFont("main.fmf")

printResult("Hello")
printResult("Hello ")
printResult("Hello  ")
printResult(" ")
printResult("  ")


Function printResult(t:String)
	Print "~q" + t + "~q is " + font.GetTxtWidth(t) + "px wide"
End Function


Results here (bearing in mind a space is 17px wide):
"Hello" is 139px wide  <<correct
"Hello " is 139px wide  <<wrong - should be 155/156
"Hello  " is 155px wide  <<wrong - should be 171/172
" " is 17px wide  <<correct
"  " is 33px wide  <<correct



Brucey(Posted 2014) [#2]
What does FontMachine do? Is it just something for packing characters in a bitmap?


Derron(Posted 2014) [#3]
I think it is an bitmapfont "creator" + a module for displaying.


Albeit one might think that such a module is easy to do alone: things like rotation, "justified texts" etc are not that easy to do in all cases. (Justification is still missing in my bitmapfont-file but I have coloring, bold and italic fonts which can get mixed in one text-draw-command ).

The benefit of fontmachine is the editor+module-symbiosis.
(I just render the TTFs files on a texture and apply customizeable effects like gradients, blurred shadows... on it - nothing for realtime, but avoids the need of a 3rd party tool).


bye
Ron


Brucey(Posted 2014) [#4]
At a guess, I'd say that SPACE drawwidth is zero, therefore when it comes to draw the last character, it removes SPACE's real width, and then adds nothing - so you are one SPACE size short in your width.

If privatedata.face[lastchar] <> Null Then
	twidth = twidth - privatedata.face[lastchar].charwidth
	twidth = twidth + privatedata.face[lastchar].drawwidth
End If		


... maybe.


GfK(Posted 2014) [#5]
What does FontMachine do? Is it just something for packing characters in a bitmap?
It's a font editor/module by Ziggy the BLIde guy.

At a guess, I'd say that SPACE drawwidth is zero
That's what I thought at first, but if I put TWO spaces at the end, the returned width changes. Likewise if I check the width of a string which ONLY has a space in it, it returns 17 - the correct width.


Derron(Posted 2014) [#6]
Seems some algorithm wants to be clever.

I think it wants to avoid "hello_space" to align different, so it auto skips "single spaces at the end" (like a trim, trimming all spaces to one).
Maybe it also is connected to some "Multiline"-algorithm (auto wrap).


@sourcecode
is it available? Thought is was a sold module with just the editor being free (or is it opposite? nvm.).


bye
Ron


ziggy(Posted 2014) [#7]
Last character is measured by character drawing size, instead of character spacing size (as the module needs to handle character overlapping properly). the problem is that space is considered a zero size drawing size, while it maybe shouldn't
to fix this, on line 265 or file BitMapFont.bmx, you could change this:
If privatedata.face[lastchar] <> Null Then

With this:
If lastchar <> 32 and privatedata.face[lastchar] <> Null Then

I think this should get rid of the issue.

EDIT: By the way, the module is open source and freely available here: http://www.blide.org/?p=145


Derron(Posted 2014) [#8]
Thanks... had a look not but saw that you cook with water too - so nothing to steal on ideas for my bitmap drawing routines :D

Seems your monkey code is more advanced concerning text alignment, text box drawing etc. .


bye
ron


ziggy(Posted 2014) [#9]
Seems your monkey code is more advanced concerning text alignment, text box drawing etc. .
Yes it is. I did wrote FontMachine module a long time ago, and it should require some refactoring.

what I see is that a character drawing size is usually the same as a character drawing offset + a space drawing size, so this:
"Hello"
and this:
"Hello "
do take usually the same drawing size... Not sure why, but it's calculated separately and it produces same result... ??

As instance, if the "o" character has a drawing offset of 10 pixels, if it's drawing size is 17, that usually means that space size is 7 pixels wide too, so it happens to be a coincidence of some kind. Not sure why, it may be related to font kerning, but I *think* there's no bug on the module calculations. If anyone has any other ideas... I'm open to explore other possible causes to this suspicious coincidence

Just being curious, what does this mean in english? " had a look not but saw that you cook with water too" (I can only understand the literal transation)


Brucey(Posted 2014) [#10]
had a look not but saw that you cook with water too

I think it means that basically you both do it the same way.


Derron(Posted 2014) [#11]
Hmm dict.leo.org suggests "Everyone puts their pants on one leg at a time."

Sorry that I thought this idiom is globally the same :D... especially as it describes the situation very vell ("you too are using just water to cook your meal, no magic ingredients").


bye
Ron


Brucey(Posted 2014) [#12]
Explaining one idiom with another is always a good thing in my book :-)


GfK(Posted 2014) [#13]
@Ziggy - thanks, I'll give that a shot tomorrow.

I'm really not very good with other people's code, is the problem.


ziggy(Posted 2014) [#14]
had a look not but saw that you cook with water too
Well, here everybody cooks with olive oil onless you're ill or something, that's why I didn't got it! XD

I'm really not very good with other people's code, is the problem.
I your defense, FontMachine code is not as elegant as I would like it to be


GfK(Posted 2014) [#15]
@Ziggy - that code mod didn't fix it. It actually made it worse!

Same code as above, different results - it now says the string is one pixel smaller with a space at the end.

"Hello" is 139px wide
"Hello " is 138px wide
"Hello  " is 154px wide
" " is 16px wide
"  " is 32px wide


[edit]

Can you tell me what this was meant to be doing? Because I commented it out and it seems to be working OK now...
		If lastchar >= 0 And lastchar < Self.PrivateData.Face.Length Then
				If lastchar <> 32 And privatedata.face[lastchar] <> Null Then
					twidth = twidth - privatedata.face[lastchar].charwidth
					twidth = twidth + privatedata.face[lastchar].drawwidth
				End If		
		End If



Derron(Posted 2014) [#16]
I think it is something like:
- is the char valid (boundary check)
- if char is not a space char and there exists a definition for it
- advance width by the given offset (-charwidth + drawwidth)

so in short it sounds like "is there a definition for this char, use it to recalculate the width)

So if commenting out that portion changes something, the "privatedata" might create trouble because a "space" has a charwidth but no drawwidth or vice versa.

There is something like the position in the string missing (only do this for not-the-last-char or only do this for the-last-char).


bye
Ron


ziggy(Posted 2014) [#17]
Characters have a drawing size, that is the size of the bitmap for this character, and a offset, that is the distance to the next character in the string. This allows characters to be properly aligned and overlap when required. that allows itallic fonts, script fonts, etc to be drawed properly.

Then, when calculating string drawing size, you need to sum all drawing offsets and latest character size, to get the size of the string, so the latest character has to be calculated differently.

So, are you sure the results are wrong? Have you tried it graphically?


Derron(Posted 2014) [#18]
This procedure assumes that the last chars "width" is bigger than the overlapping area of the char before. Not a problem in most cases - but with big slants in the font glyph it might create some trouble. This is not the case in GfKs problem.


Eg. "fun " - "n" has some graphical lines added. The "n" itself has a glyph width of 100pixels, but the next char could be drawn already after 20 px - so your values are 100 and 20. If the next char is a space it might ignore the fact that the own "glyph" width of maybe 30 px will ignore 50 px of the character before. (hope I somehow explained it understandable)


Maybe something in that lines is creating the trouble.


bye
Ron


GfK(Posted 2014) [#19]
I don't get why the last character width has to be calculated any differently to all the others? Which is why I deleted the chunk of code above to see what happened.

Widths are being calculated perfectly now (it seems). The only other issue I've noticed is that text isn't drawn precisely where I try to draw it. Instead, it's being drawn about ten pixels to the right - but this is nothing to do with my little modification - it did it before I changed anything, and FontMachine for Monkey does it, too.

I haven't dug into the FontMachine file format to investigate exactly why this could be. I had assumed that the font image file glyphs are tightly packed with no surrounding space, but this little blip makes me think maybe I was wrong and each character is scaled up to ^2 dimensions.

[edit]

Strict

Import blide.fontmachine

Graphics 1024, 768
SetClsColor 128, 128, 128
SetBlend ALPHABLEND

Global font:TBitmapFont = LoadBitmapFont("menufont.fmf")

While Not KeyDown(KEY_ESCAPE)
	Cls
	
	drawSomeText("Foobar", 100, 100)
	
	Flip
Wend

Function drawSomeText(txt:String, x:Int, y:Int)
	SetAlpha 1
	font.DrawText(txt, x, y)
	SetAlpha 0.5
	DrawRect(x, y, font.GetTxtWidth(txt), font.GetFontHeight())
End Function



Derron(Posted 2014) [#20]
It has to get calculated differently because:

- each character has a visible width (the pixels) and an "advance by x pixels" value
- if the character is between other characters, it uses the "advance by x pixels" value, else it uses the "visible width"

so if printing "HELLO" at x=0

it does:
x = 0
H: x :+ advanceWidth
E: x :+ advanceWidth
L: x :+ advanceWidth
L: x :+ advanceWidth
O: x :+ visible width

This is done to make kerning possible (a character "x" can have different space to other characters, so "xi" has different kerning pairs than "xn" -> the space between x-i and x-n could be different)

You use this to make texts look "smoother" / "easier to read".


bye
Ron


ziggy(Posted 2014) [#21]
@Gfk: The bug you're seeing now is caused by you commenting the lines you thought where "fixing" the issue. Please, uncomment them and tell me wheter it works now or not. Also, the margins are made by the font itself. Each font define some spacing/margin values.

@Derron: I couldn't explained better


GfK(Posted 2014) [#22]
No, as I said, that problem is still there in unmodified fontmachine (and in the Monkey version too).


ziggy(Posted 2014) [#23]
Please, can you send me your fmf?


GfK(Posted 2015) [#24]
Totally forgot about this thread.

I was making some additional builds of Crime Solitaire 2 recently and noticed this offset problem again, which didn't exist when I first built Crime Solitaire 2. So, I reverted back to an older version of Fontmachine and this "offset" bug has disappeared.

I don't think there's any benefit in sending you a FMF file, as it does it with all of them.


ziggy(Posted 2015) [#25]
Older version of the module, or older version of the editor? If you could tell me which versions too, I could do a better regression testing of the whole thing


GfK(Posted 2015) [#26]
older version of the module. Is there a function call to retrieve the version number?

[edit] Never mind - found it.

On this PC I have 1.5.1, which gives better results than the screenshot above, but is still not quite right.

Just to recap, here's the sample code:
Strict

Import blide.fontmachine

Graphics 1024, 768
SetClsColor 128, 128, 128
SetBlend ALPHABLEND

Global font:TBitmapFont = LoadBitmapFont("menufont.fmf")

While Not KeyDown(KEY_ESCAPE)
	Cls
	
	drawSomeText("Foobar", 100, 100)
	
	Flip
Wend

Function drawSomeText(txt:String, x:Int, y:Int)
	SetAlpha 1
	font.DrawText(txt, x, y)
	SetAlpha 0.5
	DrawRect(x, y, font.GetTxtWidth(txt), font.GetFontHeight())
End Function

Here's the result with FontMachine module latest version (whatever that is):


And here's the result with v1.5.1:



As you can see, this version has extra pixels on BOTH sides of the text.

I'll have to dig out the 'working' version (to get the version number) I used for Crime Solitaire 2 tomorrow as it's on my other PC.


ziggy(Posted 2015) [#27]
You used the same FMF document, so the only difference is the module being used, isn't it?


GfK(Posted 2015) [#28]
I'm sure I tried all variations, but I'll verify that tomorrow.


ziggy(Posted 2015) [#29]
Thanks!


GfK(Posted 2015) [#30]
It turns out the version I used for Crime Solitaire, is actually 1.5.1.

I think you might be right and there was some discrepancy with older .fmf files and newer FontMachine versions as I can't get it to happen now.

So, not really sure what's going on but I know for a fact I tested the latest FontMachine version in Monkey as well, and had an identical problem.

Maybe I dreamed all this...