Planning the Spontaneous

It's more than just a blueprint.

Print(TrueTypeFont)

Posted by Robert Chow on 16/10/2009

This problem’s been at me for the last couple of weeks.  And it’s now solved.  I don’t know whether to cry, or whether to jump for joy.  Either way, I’ll get odd looks in the office.

The Tao Framework is a very thin wrapper around the OpenGL libraray, and doesn’t explicitly support any easy way out of font rendering.  “Font rendering?”, I hear you say.  A LOT harder than it sounds.  In fact, a lot of things is a lot harder than it sounds – why do we bother?!  Anyway.  It’s more than just typing a couple of characters, and expecting them to appear on the screen – you have to find the character first, in the character library which you’ve had to either pull apart, or create yourself before you can draw it.  But after two weeks, many leads and dead-ends, it’s been solved.  And there’s a story behind it all.

Bitmap Fonts

These are pretty much the basic of the basics.  Each character is just a bitmap image, mono-color, and you can’t really do much with them either.  I was fairly happy to have achieved this hurdle – it took me a fair while to figure out what was happening and how.  Before you can pull the bitmap image out, you have to create the font first.

font = Gdi.CreateFont(-36,                // Height
0,                                        // Width
0,                                        // Escapement
0,                                        // Orientation
400,                                      // Weight
false,                                    // Italic
false,                                    // Underline
false,                                    // Strikeout
Gdi.ANSI_CHARSET,                         // Character Set
Gdi.OUT_TT_PRECIS,                        // Output Precision
Gdi.CLIP_DEFAULT_PRECIS,                  // Clipping Precision
Gdi.ANTIALIASED_QUALITY,                  // Output Quality
Gdi.FF_DONTCARE | Gdi.DEFAULT_PITCH,      // Pitch and Family
“Calibri”);                               // Face

Wgl.wglUseFontBitmapsA(User.GetDC(simpleOpenGlControl.Handle), 0, 255, fontbase);

You’re then (as far as I’m aware) using display lists (I’m not too sure on how these work… but I will do sooner than later) to call up each character, and you have to translate each time with a look up to the character width/height before drawing the next character.  The code’s not very nice, so I’m going to save you (and myself) the trouble.

Outline Fonts

These are setup exactly the same way, with one change really.  Instead of initiating a list of bitmaps we ask for outlines instead.

Wgl.wglUseFontOutlinesW(User.GetDC(simpleOpenGlControl.Handle), 0, 255, fontbase, 0, 0.2f, Wgl.WGL_FONT_POLYGONS, gmf);

The great thing that outlines offer that bitmaps don’t, is the flexibility they provide – they are 3D fonts, and therefore can be manipulated the same way a normal object in OpenGL can – that’s includes scaling, translating, rotating, lighting etc.

But in terms of quality, with basic anti-aliasing on, not so nice, and same with bitmap fonts too.  In fact, they’re both pretty much the same.

ISE.FreeType

This library is pretty much a wrapper around the Tao.Freetype library – the one used to “create” (loose term) fonts.  Score.  And it works as well.

Oh wait.

The license states that we can’t use it.  Got to go looking again.

FTGLSharp

There’s a library that works with C/C++ called FTGL, and someone, very nicely has created a wrapper around it so it would be compatible with C#.  The great thing about FTGL is it essentially has everything you need to render fonts onto the screen, with all the flexibility, and the appropriate license!  The problem is the wrapper itself – it was a struggle to compile, and even after then it was a huge struggle to get something on the screen – in fact, that bit didn’t even happen.

Frustrating really.  All of these amazing leads, and you think it’s all good to go, but then you get shot down for being too eager.  If you want a job doing, do it yourself.  So I did.  After poking around source code of solutions that ultimately didn’t work, I managed to create a solution.

Font Rendering

So this approach I’ve gone for is texture-mapped fonts – load a character, and draw it to texture.  The only problem is space – a way to solve that is texture atlasing, but that’s another chapter in the book.

The first thing to do is to make sure you have a reference to the FreeType library, and then using the library pointer, create a pointer reference to the font face.

FT.FT_Init_FreeType(out LibraryPtr);
FT.FT_New_Face(LibraryPtr, trueTypeFilePath, 0, out FacePtr);

This is the tricky part.  Now that we have a pointer to the font face, we have to extract the information.  It’s in the form of a struct, so we marshal the information so it’s useable.

FT_FaceRec faceRec = (FT_FaceRec)Marshal.PtrToStructure(FacePtr, typeof(FT_FaceRec));

Following that, we set the character size/pixel height. I have used my own function to determine the pixel height of the font point size.   Why?  Well, when you choose a font size in a text editor, you’re normally given the choice of point sizes, pt.  These point sizes do not directly correspond to the font height – the font height is actually x4/3 the size of the point.  In this method of producing the fonts, we take the max point size – I have set this to 96 (height is 128 – it’s a power of 2).  The functions also require the dots per inch (DPI) of the screen – I presume this is for detail.

FT.FT_Set_Char_Size(FacePtr, 0, MAX_POINT_SIZE * 64, (uint)DPI, (uint)DPI);
FT.FT_Set_Pixel_Sizes(FacePtr, 0, (uint)GetFontHeight(MAX_POINT_SIZE));

We then take the number of glyphs (essentially a character) this true type font has to offer, and create an array to store the character information.

Characters = new TTCharacter[faceRec.num_glyphs];

Now that we have the font set up, we have to set up each character.  This is done on the fly, so each character is only created if it’s needed – saving on space and time.  The TTCharacter class I have used to store the character information will hold the texture ID the character is drawn onto, and the glyph metrics – these are character unique, describing the width, height, offsets and so forth, of each character.  As each glyph is created from the face, it gives a bitmap type structure to use to draw the character.  This structure we will need to copy to texture, so we can use it for ourselves.  So as we create each TTCharacter, we also generate a texture ID.

Characters[c] = new TTCharacter(c, faceRec.num_glyphs);
Gl.glGenTextures(1, out Characters[c].TextureId);

We then ask the face to load up the appropriate glyph.  This, similar to the face, is in the form of a pointer, so it too needs to be marshalled out into a useable state.  The glyph properties are also copied into the TTCharacter class.

FT.FT_Load_Char(FacePtr, c, FT.FT_LOAD_RENDER);
glyphSlotRec = (FT_GlyphSlotRec)Marshal.PtrToStructure(faceRec.glyph, typeof(FT_GlyphSlotRec));
FT.FT_Render_Glyph(ref glyphSlotRec, FT_Render_Mode.FT_RENDER_MODE_NORMAL);
Characters[c].Metrics = glyphSlotRec.metrics;

Now we have the glyph, we can take the bitmap and copy it to texture.  There is only one problem tho – and this confused the hell out of me when I came across it.  I  first started to draw the texture as it was, without having modified it.  And the results weren’t pretty.  The bitmap needs to be adapted to a format OpenGL likes – that is, a texture with the width and height of a power of 2.  So we transpose it into the new structure, before copying it to texture.  Note: I’ve used an extension method to find the next power of 2! And also, lambda expressions for the for loops!

byte[] bmp = new byte[(glyphSlotRec.bitmap.width) * glyphSlotRec.bitmap.rows];
Marshal.Copy(glyphSlotRec.bitmap.buffer, bmp, 0, bmp.Length);

int texSize = (glyphSlotRec.bitmap.rows.NextPowerOf2() < glyphSlotRec.bitmap.width.NextPowerOf2()) ? glyphSlotRec.bitmap.width.NextPowerOf2() : glyphSlotRec.bitmap.rows.NextPowerOf2();

byte[] tex = new byte[texSize * texSize];
texSize.Times((j) => texSize.Times((i) =>
{
if (i < glyphSlotRec.bitmap.width && j < glyphSlotRec.bitmap.rows)
{
tex[j * texSize + i] = bmp[j * glyphSlotRec.bitmap.width + i];
}
}));

Gl.glBindTexture(Gl.GL_TEXTURE_2D, Characters[c].TextureId);
Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_WRAP_S, Gl.GL_CLAMP_TO_EDGE);
Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_WRAP_T, Gl.GL_CLAMP_TO_EDGE);
Gl.glTexParameterf(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MAG_FILTER, Gl.GL_LINEAR_MIPMAP_LINEAR);
Gl.glTexParameterf(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MIN_FILTER, Gl.GL_LINEAR_MIPMAP_LINEAR);
Glu.gluBuild2DMipmaps(Gl.GL_TEXTURE_2D, Gl.GL_ALPHA8, texSize, texSize, Gl.GL_ALPHA, Gl.GL_UNSIGNED_BYTE, tex);

You’ll notice that the lines at the end of the code above is a lot different to the code where I render to texture again and again.  This is because this method incorporates mipmapping.  Mipmapping is the process of taking a large image, and then scaling it down to the next power of 2 size, until we reach a 1×1 size.  As the image is resized, each pixel is sampled multiple times, therefore trying to keep precision in quality, whilst producing a smaller sized image.  This saves from having to add extra anti-aliasing functionalities, to stop images from looking blocky – this will happen when there is more, or less, than 1 texel fighting for a pixel.  Therefore, as a texture is called up, it will select the appropriate size of mipmap and load that up, instead of the original, and thus preserving some form of quality.  Expensive in memory, yes, but it’s the best I have so far.  The only other option is to produce each character multiple times for each point size – without mipmapping.  This will cost a lot more in memory.

Lastly, is to draw the textures.  As the textures are stored as alpha values (see in the code above), we don’t need to do much else, but just draw the texture.  Similarly to all other text renderers, after each character, we will have to find the character width and offsets, and translate in order to be able to draw the next character.

Results are acceptable – they’re not quite MS Word standard, but they’re a lot better than mono-colour.

Font ComparisonFont Comparison.  24pt normal weight, from right to left: MS Word, my font system and mono-colour bitmap font; from top to bottom: Calibri, Arial, Verdana and Times New Roman.

Font ProblemFont Problem.  The top shows what it should have looked like.  The bottom not so – this is what it looked like before copying the bitmap into an OpenGL friendly format – it’s not nice!

Mip Mapping.  Shows how a full scale image can be downsized whilst still maintaining a quality.  This image has been taken from http://www.actionscript.org/forums/showthread.php3?t=194116

It’s not perfect – but it’s a start nevertheless.   And the main thing to do is to make sure that the current solution is scalable – that way a new solution to producing fonts can be easily added into the system, without having to change much of the code that’s already there.

On a more personal note – I had my first driving lesson yesterday.  Can’t say I found it easy.  But we’ll have to see how that goes for the future.  Either way, at least it’s a start.


Advertisements

3 Responses to “Print(TrueTypeFont)”

  1. […] to look elsewhere – I am no expert of OpenGL.  Essentially, if you refer back to when I was creating fonts, it uses mip-mapping as a way of keeping detail and quality as images are getting smaller and […]

  2. Hiya, great day.. Your work is incredibly uplifting. I never thought that it was possible to do something like that until after I read your content. You certainly gave a great understanding on how this whole system performs. I will always come back for more information. Keep writing!

  3. euanmacinnes said

    Just want to check with you what you thought the license issues were with ISE.FreeType? I’m the author, I thought I had it under freeware, if there’s a problem with the current license, let me know, it’s supposed to be freeware.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
%d bloggers like this: