Sign in to follow this  

Font kerning information with Font rendering

This topic is 2540 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I am generating a font sheet where I know the X and Y location of the characters on it, along with other information pertaining to the font name and font size. However, I am having a problem with piecing this information together to make something renderable. To obtain the left/right bearing information and regular text metrics (for the height), I use GetCharABCWidthsFloat and GetTextMetricsW.

Does anyone have any suggestions on how to using the font information to piece together a renderable representation of fonts? A good example of this is the sequences "ft" and "nj" in Times New Roman. Inthe sequence "ft", the 'f' will seem like it is coupling the 't' and with "nj" it will appear that the 'j' is hooking under the 'n'.

Share this post


Link to post
Share on other sites
I use freetype2 for font rendering. It provides and uses kerning information, too.

I guess a simple way would be to maintain a list of offsets depending on the character to the left, i.e. for the j keep the standard offset as well as another offset for the n (and possibly other characters as well, maybe in that case the default would be a smaller offset and a bigger one for characters like j,g,y, etc.).

That won't always work for different fonts, however (like arial n and times j), but I assume that's not an issue right now.

Share this post


Link to post
Share on other sites
I able to get kerning information through the gdi dll's, so I don't need to use freefont. I am sure there is an algorithm out there somewhere, I have just not come across it yet.

Share this post


Link to post
Share on other sites
I call "GetCharABCWidthsFloatW" to get the width information for a character. With this I will get the leftside bearing, right side bearing and the advanced width.

For positioning each characters, I will start with a "xPlacement" variables that will begin at zero. I will first adjust the xPlacement variable by subtracting the "left side bearing". After the character is drawn, I will then advance it by the width of the character( I will show the calculation for this later). I will then move the xPlacement variable by adding the "right side bearing" information from the current "xPlacement".

This, in my opinion is all that should be code for character placement, correct?

An important thing is to correct the width of the characters. The width will be calculated by taking the advancedWidth, plus the POSITIVE version of the left side bearing and the POSITIVE version of the right side bearing. I will convert this values to positive, if they are negative so I can have the total width of the character.

Here is some pseudo code about how it is generated.


float xPlacement = 0.0f;
for(int i = 0; i < strlen(text); ++i)
{
char charValue = text[i];
GetCharWidthABC(.., .., charInfo);

float posLeft = charInfo.leftSideBearing;
if(charInfo.leftSideBearing < 0)
posLeft = -charInfo.leftSideBearing;

float posRight = charInfo.rightSideBearing;
if(posRight < 0)
posRight = -charInfo.rightSideBearing;

float posWidth = posRight + posRight + charInfo.advancedWidth;

float letterWidth = posWidth;

xPlacement -= charInfo.leftSideBearing;

(.... generated some vertex coordinates, using the xPlacement variable and letterWidth ...)

xPlacement += letterWidth;
xPlacement += charInfo.rightSideBearing
}

Does this appear to be the correct way to do this?

Share this post


Link to post
Share on other sites
You never say what exactly the problem is... However I think you're confused about what GetCharABCWidths returns. A and C are the left & right side bearings and B is the black width, not the advance width.

Assuming I'm reading your code right, you want to set posWidth equal to charinfo.abcB and xPlacement += abcA + abcB + abcC. Most of your logic looks unecessary unless you need it for some reason other than letter placement.

If you actually need kerning info, the simplest way to get it is via GetKerningPairs.

Share this post


Link to post
Share on other sites
Quote:
Original post by Anon Mike
You never say what exactly the problem is...


Sorry about that.

I am trying to render the ASCII characters 0-256 to a bitmap image (which I can do). The problem I am having is taking each individual characters and adjusting them for letter placement.

Quote:
Original post by Anon Mike
I think you're confused about what GetCharABCWidths returns. A and C are the left & right side bearings and B is the black width, not the advance width.


What is the point of left and right bearings if not for kerning?

Quote:
Original post by Anon Mike
If you actually need kerning info, the simplest way to get it is via GetKerningPairs.


I went ahead and tried this, however, I believe that I am doing it wrong as it is not adjust the characters correctly (I scaled down the values since they were enormous). Do you have any suggestions to what I may be doing wrong?


int size = (16 * 12) / 4;

HDC dc = CreateCompatibleDC(0);
GLYPHMETRICS gm;
MAT2 mat;
mat.eM11.fract = 0; mat.eM11.value = 1; mat.eM21.fract = 0; mat.eM21.value = 0;
mat.eM12.fract = 0; mat.eM12.value = 0; mat.eM22.fract = 0; mat.eM22.value = 1;

HFONT fnt = CreateFont(size,0,0,0,FW_NORMAL,0,0,0,ANSI_CHARSET,OUT_TT_PRECIS,CLIP_DEFAULT_PRECIS,PROOF_QUALITY,FF_DONTCARE,"Arial");
SelectObject (dc, fnt);

int nKerningPairs = GetKerningPairs(dc, 0, 0);
KERNINGPAIR * kerningpairs = new KERNINGPAIR[nKerningPairs];
GetKerningPairs(dc, nKerningPairs, kerningpairs);

for(U32 c = 0; c < numberOfCharacters; ++c)
{
fontResource->GetCharcterInfo(m_Text[c], charInfo);


float posLeft = charInfo.leftSideBearing;
if(charInfo.leftSideBearing < 0)
posLeft = -charInfo.leftSideBearing;

float posRight = charInfo.rightSideBearing;
if(posRight < 0)
posRight = -charInfo.rightSideBearing;

float posWidth = posRight + posRight + charInfo.advancedWidth;

letterWidth = (posWidth * 0.01f);

if(c != 0)
{

DWORD BytesReq = GetGlyphOutlineW(dc, m_Text[c], GGO_GRAY8_BITMAP, &gm, 0, 0, &mat);
U8 * glyphImg= new U8[BytesReq];
DWORD r = GetGlyphOutlineW(dc, m_Text[c], GGO_GRAY8_BITMAP, &gm, BytesReq, glyphImg, &mat);

for (int k=0; k<nKerningPairs; k++)
{
if ((kerningpairs[k].wFirst == previousCharIndex) && (kerningpairs[k].wSecond == m_Text[c])) {
letterBottomLeftX+= (kerningpairs[k].iKernAmount* 0.01f);
break;
}
}
letterBottomLeftX+= (gm.gmCellIncX * 0.01f);
}

letterBottomLeftX += letterWidth;
previousCharIndex = m_Text[c];
}

Share this post


Link to post
Share on other sites
Quote:
Original post by simotix
Quote:
Original post by Anon Mike
You never say what exactly the problem is...

I am trying to render the ASCII characters 0-256 to a bitmap image (which I can do). The problem I am having is taking each individual characters and adjusting them for letter placement.

What does "adjusting them for letter placement" mean? I'm *guessing* that you are seeing bad letter spacing and this is what you're trying to fix.

Quote:
What is the point of left and right bearings if not for kerning?

The side bearings and kerning both tweak letter spacing, but they are different concepts. Negative side bearings are used to account for the fact that advance width may be less than black width, typically because a portion of the glyph overhangs into the space where the adjacent glyphs would be. In practice you see this a lot with italic fonts and or decorative fonts with fancy swashes & such.

Kerning on the other hand exists because specific sets of glyphs sometimes look strange if they're rendered with the default letter spacing. Putting 'A' and 'V' next to each other is the canonical example. The tend to look to far apart so kerning is used to push them together a bit. In practice very few apps bother with kerning, on Windows at least. It's very unlikely your problem has anything to do with kerning.

Quote:
I went ahead and tried this, however, I believe that I am doing it wrong as it is not adjust the characters correctly (I scaled down the values since they were enormous). Do you have any suggestions to what I may be doing wrong?

I still suspect you are confused about what the 'B' width returned by GetCharABCWidths means. It is not the advance width. The advance width is the sum of A+B+C. I also suspect that whatever font "GetCharacterInfo" is working with is different than the HFONT you create a few lines above. The fact that you say you have to manually scale the results you get from GetKerningPairs tends to support this.

Share this post


Link to post
Share on other sites
Quote:
Original post by Anon Mike
Quote:
Original post by simotix
Quote:
Original post by Anon Mike
You never say what exactly the problem is...

I am trying to render the ASCII characters 0-256 to a bitmap image (which I can do). The problem I am having is taking each individual characters and adjusting them for letter placement.

What does "adjusting them for letter placement" mean? I'm *guessing* that you are seeing bad letter spacing and this is what you're trying to fix.


When I say adjusting for letter placement, I mean for coupling together. For example, if I have the letters "n" and "j" in the font "Times New Roman", the hook of the j should go under then.

To account for this, I would need to where the "j" will be rendered. For example, with the letters "n" and "j", and they are built with rectangles with the width of 1, I would render "n" at the position (0, 0, 0). However, since the width of the rectangle "n" will be one, I would then render "j" at (1, 0, 0). What I need to calculate is two things.

1) I would need to adjust the "j" so that way it takes in to account whatever information it needs so the "j" would hook under "n" in a font like Times New Roman.

2) The actual width of the rectangle for the character. For example, the width of the rectangle for "@" should not be the same as "i" for most fonts.

Share this post


Link to post
Share on other sites
Since you cannot rely on the Right Way To Do It (asking a competent implementation like GDI or Freetype to lay out and render a whole string, with hinting and subpixel positioning, taking care for you not only of kerning but also of OpenType ligatures and other features) you should query the font for the full lists of metrics and kerning pairs, which are very simple tables within the TrueType/OpenType font, and place them in the metadata that go with your spritesheet.

Then you know everything you need to implement variable spacing and kerning: the distance between the left side of the "box" of consecutive characters is the previous character's advance width plus (or minus) the kerning adjustment if there is a kerning pair for those characters.

Share this post


Link to post
Share on other sites
Quote:
Original post by LorenzoGatti
Since you cannot rely on the Right Way To Do It (asking a competent implementation like GDI or Freetype to lay out and render a whole string, with hinting and subpixel positioning, taking care for you not only of kerning but also of OpenType ligatures and other features) you should query the font for the full lists of metrics and kerning pairs, which are very simple tables within the TrueType/OpenType font, and place them in the metadata that go with your spritesheet.

Then you know everything you need to implement variable spacing and kerning: the distance between the left side of the "box" of consecutive characters is the previous character's advance width plus (or minus) the kerning adjustment if there is a kerning pair for those characters.


The problem is, I do not know what to query and then what the algorithm is to apply it.

Share this post


Link to post
Share on other sites
Quote:
Original post by Anon Mike
There's nothing you need to query other than what is already returned to you by GetCharABCWidths. 'j' in Times New Roman 12pt has an 'A' width of -1 for example.


Interesting, perhaps I was trying to make it more difficult than it really was.

How would you recommend calculating the width? I was going to just use the "B" width and scale.

Share this post


Link to post
Share on other sites
I was hoping to avoid using FreeType or other libraries as this functionality should work with windows functions.

I am extremely close, although I do not see any adjustment for kerning information. For example, in Times New Roman it seems like "nj" would have kerning information, however, I am getting that there is no kerning amount. The only thing that is adjust for a combination like "nj" is GLYPHMETRICS.gmCellIncX .

Here is what I am doing, is there any suggestions?


int previousCharIndex = -1;
int size = 48;

HDC dc = CreateCompatibleDC(0);
GLYPHMETRICS gm;
MAT2 mat;
mat.eM11.fract = 0; mat.eM11.value = 1; mat.eM21.fract = 0; mat.eM21.value = 0;
mat.eM12.fract = 0; mat.eM12.value = 0; mat.eM22.fract = 0; mat.eM22.value = 1;

HFONT fnt = CreateFont(size,0,0,0,FW_NORMAL,0,0,0,ANSI_CHARSET,OUT_TT_PRECIS,CLIP_DEFAULT_PRECIS,PROOF_QUALITY,FF_DONTCARE,"Times New Roman");
SelectObject (dc, fnt);

int nKerningPairs = GetKerningPairs(dc, 0, 0);
KERNINGPAIR * kerningpairs = new KERNINGPAIR[nKerningPairs];
GetKerningPairs(dc, nKerningPairs, kerningpairs);

float letterBottomLeftX = 0.0f;
float letterHeight = 1.0f;
float letterWidth = 1.0f;
float previousRightSideBearing = 0.0f;

float scale = 0.05f;
for(U32 c = 0; c < numberOfCharacters; ++c)
{
fontResource->GetCharcterInfo(m_Text[c], charInfo);

letterWidth = charInfo.advancedWidth * scale;
letterHeight = textMetrics.Height * scale;

if(c != 0)
{
DWORD BytesReq = GetGlyphOutlineW(dc, m_Text[c], GGO_GRAY8_BITMAP, &gm, 0, 0, &mat);
U8 * glyphImg= new U8[BytesReq];
DWORD r = GetGlyphOutlineW(dc, m_Text[c], GGO_GRAY8_BITMAP, &gm, BytesReq, glyphImg, &mat);

for (int k=0; k<nKerningPairs; k++)
{
if ((kerningpairs[k].wFirst == previousCharIndex) && (kerningpairs[k].wSecond == m_Text[c])) {
letterBottomLeftX += (kerningpairs[k].iKernAmount * scale);
break;
}
}

letterBottomLeftX -= (gm.gmCellIncX * scale);
}

// set vertex information

// set uv's

// set indicies

letterBottomLeftX += letterWidth;
previousCharIndex = m_Text[c];
}

Share this post


Link to post
Share on other sites
I got sick of tweaking these numbers and constantly finding problem pairs, so I wrote my own kerner in the end. I have a [256][256] array of character advances and I look that up with the current letter and the next one.

To get this array filled in, I render each letter to a blank image and then blur it by one or more pixels (based on the average size of the letters, so bigger fonts blur out more. The actual heuristic for this took some fiddling).

Then I test render them one over the other and check for a collision. If there is, I move the 2nd one right another pixel and check again. It takes about a minute to do all 64K of combos and it works absolutely perfectly.

Share this post


Link to post
Share on other sites
Quote:
Original post by LorenzoGatti
Maybe you get no kerning pair because kerning is disabled in your HDC or HFONT.
Try rendering a whole string to see if it is kerned like it should.


I end up getting kerning pairs for some values, just no ones like "nj".

Quote:
Original post by Rubicon
I got sick of tweaking these numbers and constantly finding problem pairs, so I wrote my own kerner in the end. I have a [256][256] array of character advances and I look that up with the current letter and the next one.

To get this array filled in, I render each letter to a blank image and then blur it by one or more pixels (based on the average size of the letters, so bigger fonts blur out more. The actual heuristic for this took some fiddling).

Then I test render them one over the other and check for a collision. If there is, I move the 2nd one right another pixel and check again. It takes about a minute to do all 64K of combos and it works absolutely perfectly.


Do you have a code example of this? This seems like it could expensive and for custom fonts with specific kerning, it seems like this could be wrong.

Share this post


Link to post
Share on other sites
Quote:
Original post by simotix
Quote:
Original post by Anon Mike
There's nothing you need to query other than what is already returned to you by GetCharABCWidths. 'j' in Times New Roman 12pt has an 'A' width of -1 for example.

Interesting, perhaps I was trying to make it more difficult than it really was.

Yeah, I've said that two or three times now...

Quote:
How would you recommend calculating the width? I was going to just use the "B" width and scale.

Which width are you talking about? There are at least three, and possible more, that are relevant.
  • The 'A' width, i.e., the left side bearing.
  • The black width, i.e., the width of the bitmap needed to hold the glyph
  • The advance width, i.e., the distance the cursor gets moved after drawing a glyph

The data returned by GetCharABCWidths gives you all these. The 'A' width is abcA. The black width is abcB. The advance width is abcA+abcB+abcC.

Your problem, as I understand it, has nothing whatsoever to do with kerning. Mucking around with GetKerningPairs will not help you and just confuses the issue.

FreeType also will not help you. Your problem seems to be one of properly making use of the data you have, not how you get that data in the first place.

Share this post


Link to post
Share on other sites
Quote:
Original post by simotix
Do you have a code example of this? This seems like it could expensive and for custom fonts with specific kerning, it seems like this could be wrong.
Nothing sane to post here - far too longwinded, but it should be easy enough from my description, surely. I can help with specific questions if you wish though. I have this in a standalone app that outputs the final texture page and kerning data, so speed isn't really an issue (plus a minute is worse case)

tbh I have seen a few problems with this system, so I shouldn't have used the word "perfect". It is still by far and away the best method imo though, as problem fonts never work anyway and are best avoided.

I use freetype2 to do the glyph rendering. It has better kerning info than windows and can also handle mac fonts (they are different in many ways) but its native output still seemed inferior to my blatant brute force method, possibly because they use info from the font file which isn't always correct. Especially with crappy free fonts you can find on the net.

Share this post


Link to post
Share on other sites
Quote:
Original post by Anon Mike Your problem seems to be one of properly making use of the data you have, not how you get that data in the first place.


That is correct, I am not sure how to properly use the data that I have. How would you recommend I use it? If I use it the following way, my letters still will not be placed correctly.


float letterBottomLeftX = 0.0f;
float letterHeight = 1.0f;
float letterWidth = 1.0f;

float scale = 1.0f;
for(U32 c = 0; c < numberOfCharacters; ++c)
{
fon->GetCharcterInfo(charValue, charInfo);
//float advancedWith = (charInfo.A * scale) + (charInfo.B * scale) + (charInfo.C * scale);

letterWidth = charInfo.B * scale;
letterHeight = textMetrics.Height * scale;

if(c != 0)
{
letterBottomLeftX += (charInfo.A * scale);
}

// vertex placement, beginning at letterBottomLeftX

// texture placement

// index placement

letterBottomLeftX += letterWidth;
letterBottomLeftX += (charInfo.C * scale);
}

Share this post


Link to post
Share on other sites
Quote:
Original post by Headkaze
Check out the example implementations for rendering the data generated from the AngelCode Bitmap Font Generator


I have taken a look at the AngelCode Bitmap Font generator, and I have even run the example, and it is what is doing what I am looking for. However, the values it is generating, and the values I am generating are two completely separate things.

For example, this is what they will generate for a size 48 times new roman for the charters "f" and "t".


char id=102 x=220 y=61 width=17 height=29 xoffset=0 yoffset=9 xadvance=13 page=0 chnl=15
char id=116 x=120 y=157 width=12 height=26 xoffset=0 yoffset=13 xadvance=12 page=0 chnl=15


On the other hand, I will get nothing even near that (and they don't have a kerning pair saved for the two).

For example, here is what I will get for an "f".
char = f, A = 2, B = 26, C = -7
char = t, A = 0, B = 18, C = 0

Any idea how they are generating their values?

Share this post


Link to post
Share on other sites
Quote:
Original post by simotix
Quote:
Original post by Anon Mike Your problem seems to be one of properly making use of the data you have, not how you get that data in the first place.


That is correct, I am not sure how to properly use the data that I have. How would you recommend I use it? If I use it the following way, my letters still will not be placed correctly.

You still haven't said precisely what goes wrong. You've said what you want to achieve, but you haven't said what result you're getting.

Your calculation of letterBottomLeftX looks ok. I think I understand why you're adding the various components in bits&pieces instead of all at once but it nevertheless wories me. It might be helpful to have the font, font size, and values of letterBottomLeftX and charInfo in each iteration of the loop after the call to GetCharacterInfo. As well as what you think those values should be, assuming you think they're wrong.

Share this post


Link to post
Share on other sites
Quote:
Original post by Anon Mike
You still haven't said precisely what goes wrong. You've said what you want to achieve, but you haven't said what result you're getting.


What is going wrong is I am not getting the correct placement information on how to place each individual characters. I am trying to calculate the x values at this time (letterBottomLeftX). For example, where is the result I am getting with the calculation I posted in the my previous post.



Quote:
Original post by Anon Mike
Your calculation of letterBottomLeftX looks ok. I think I understand why you're adding the various components in bits&pieces instead of all at once but it nevertheless wories me. It might be helpful to have the font, font size, and values of letterBottomLeftX and charInfo in each iteration of the loop after the call to GetCharacterInfo. As well as what you think those values should be, assuming you think they're wrong.


The values I am getting from GetCharacterInfo for charInfo are the values that I get from the GetABCWidth's, I checked to make sure they are the exact values, and they are.

EDIT: The UV's are a bit off, I am aware of this. I am concerned about the letter placement first.

Share this post


Link to post
Share on other sites
Is the horizontal stretching expected?

Assuming so, I'm pretty sure this is your problem:
		if(c != 0)
{
letterBottomLeftX += (charInfo.A * scale);
}

[...]

letterBottomLeftX += letterWidth;
letterBottomLeftX += (charInfo.C * scale);

Try changing it to:
		if(c == 0)
{
letterBottomLeftX = -(charInfo.A * scale);
}

[...]

letterBottomLeftX += (charInfo.A + charInfo.B + charInfo.C) * scale;


Your version leaves letterBottomLeftX in this wierd state where it's sitting between the old and new positions. I suspect that between your placement code and your rendering code you end up using the left side bearing twice.

If that doesn't fix things then it would be interesting to see the output for "njpjp"

Share this post


Link to post
Share on other sites

This topic is 2540 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this