tutorial 7

In this tutorial, we will cover the displaying of text using the NSGLFont. The code to use NSGLFont is actually fairly easy, but we will also be covering the creation of the NSGLFont. Here's the code that we will use to actually draw the text.


// load the identity (otherwise, the words will go along with the last rendered item)
glLoadIdentity();
// the color here will only be implemented if the lights are off
glColor3f( 0.5,0.75, 0.5 );
// our glRasterPos doens't seem to work like Nehe's // this moves the renderer away from us
glRasterPos3f( 0, 0, -1 );
// the bitmap with NULL just moves us to the relative position that we want
glBitmap( 0,0,0,0,-125,-200, NULL);
// write the text
glPrint(f, "my god... it's full of stars");

As you can see, having the text draw is simple. Somewhere in the initGL, we have to allocate and initialize our font, but that really isn't very difficult.

Now we'll look at the NSGLFont class. It inherits from NSObject. I won't be putting the entire class here (you'll have to read my comments to figure things out I suppose), but I will try to show some of the more important parts.

First, we're going to define the number of characters. For our purposes, this will be 96. If you are using a differenct character set, you may want to set this higher, but I'm only going to look at the ASCII characters for this code.


#define max_chars 96

Now let's look at NSGLFont's interface. It's a little bare, but maybe you'll come up with some really interesting things to put in here later.

The first variable, base, is an OpenGL variable that will tell us where our display lists will start from. You don't have to worry too much about that here, but it will be used to make sure that we use only the display lists for this font. This means you can have multiple fonts if you want. Next we have an NSFont *font. This font is used to make sure that we have a valid font as well as to get some of the size particulars of the characters. data, width and height all contain character specific information. data contains a bitmap of each of the characters, width and height contain thier width and height to a nearby power of 2. The x-offset contains the information of how far to the right each character should go.


@interface NSGLFont : NSObject { GLuint base; NSFont *font; unsigned char *data [max_chars]; size_t width [max_chars]; size_t height [max_chars]; unsigned int x_offset [max_chars]; size_t max_height; size_t max_width; }

Moving on to the methods of NSGLFont... You can read through the initialization of the NSGLFont class. It's fairly straightforward. This is the only OpenGL code within the initialization. It creates space for a bunch of display lists then compiles each letter within a loop.


base = glGenLists( max_chars ); ... glNewList( base+i,GL_COMPILE); glBitmap(width[i],height[i],0,0,x_offset[i],0,data[i]); glEndList();

A lot of the code in the initFont is "simple" initializations of various CoreGraphics functionality. Although the code is not difficult, it is worth mentioning in this tutorial. To create a CGContext, we first decide how many bits per component we want. In grey scale, which is what we will use for this particular implementation, Apple only supports 8 bits. Next we create a colorspace. There are valid colorspaces that have colors, but grey is the easiest for the later conversion to a bitmap. We set the alpha information to none so that we don't have a second channel of information, and then we get the number of components in the color space (which in this case is exactly 1).


bitsPerComponent = 8; colorspace = CGColorSpaceCreateDeviceGray(); alphaInfo = kCGImageAlphaNone; num = CGColorSpaceGetNumberOfComponents(colorspace);

Assuming that our color space was correctly allocated, we next find the number of bytes per row. This will be the width of your picture * number of components in the colorspace. If bitsPerComponent were not 8, then we would need more (or less) space. We the calloc the memory. We could malloc it, but in testing I found that without clearing the space I would sometimes get artifacts from other things that were in the memory.


bytesPerRow = width[i]*num; data[i] = (void *) calloc( bytesPerRow, height[i] );

Finally, we have all the information we need to create a CGContext. Assuming we've done everything properly, we should get a grayscale Core Graphics context with 8 bits per component and no alpha information. We next draw the character into the space allocated by data and set the x_offset to the maximum font advancement. This makes a font like Times look a little odd, but I don't know how to get the advancement of a particular glyph. Finally, before we can use the data, we must convert it into a bitmap. Apple does not give us an easy way to get at a bitmap representation, but luckily it isn't very difficult to convert a bytemap to a bitmap. If you were using glDrawPixels instead of glDrawPBitmap, the conversion into a bitmap would not be necessary.


cg_context = CGBitmapContextCreate(data[i], width[i], height[i], bitsPerComponent, bytesPerRow, colorspace, alphaInfo); [self drawLetter:j context:cg_context atx:0 withFont:c_fontname atSize:size]; x_offset[i] = [font maximumAdvancement].width; [self convertBitmap:i];

One thing that we must ensure with this class is that all of our space is eventually deallocated. We have to free all the data, and we have to delete all the OpenGL display lists, and we have to release our hold on the font so that it can be deallocated, and finally we have to call the super dealloc funtion (as per Apple's standards).


for( i=0; i<max_chars; i++ ) free(data[i]); glDeleteLists(base,max_chars); [font release]; [super dealloc];

The last thing we'll look at is how to actually write the text out onto the screen. We're going to use the raster position that was already defined by our drawing routine.

The first thing we'll do in this method is to find out whether the GL_TEXTURE_2D is on or not. We need to turn it off in order to draw our Bitmap font. If it was on before the method started, we'll turn it back on at the end.

Next we'll get a c string out of our NSString. The Cstring is very useful because it is an array of characters. Next we push the GL_LIST_BIT attribute and tell OpenGL that our new base of the lists is going to be our base - the lowest character (' '). Then we'll just have a call to CallLists, which will draw our text for us. We pop the GL_LIST_BIT attribute so that whatever was there before we changed the list base is put back. And that's that.


glGetBooleanv( GL_TEXTURE_2D, &tex2d ); glDisable(GL_TEXTURE_2D); c_str = [s cString]; glPushAttrib(GL_LIST_BIT); glListBase(base-' '); // 97='a' glCallLists( [s cStringLength], GL_UNSIGNED_BYTE, c_str); glPopAttrib(); if(tex2d ) glEnable(GL_TEXTURE_2D);

The last function, glPrint, is just a wrapper around the NSGLFont that converts arguments using va_start, va_end and vsprintf functions.


     Download the project builder files

 

return to deep cocoa / cocoagl tutorials