// // glFont.m // CGL // // Created by kat on Sat Jun 15 2002. // Copyright (c) 2001 Katherine Tattersall. All rights reserved. // #import "NSGLFont.h" #define TESTTIME @implementation NSGLFont // if you turn TESTTIME on, you will often not see a difference between the cheapOutlineFont and the // regular outlineFont // try creating the Webdings font at 156 and you will see a difference // outlineFont is a better quality font than cheapOutlineFont. If you use cheapOutlineFont, you will have // to be more careful about your colors. outlineFont is based on hight maps // outlineFont will not run out of polygons unless your video card does // outlineFont fonts are backless (that is, the backs are NOT solid) // I do not see this being a problem, however you can add the backs by adding z=0 squares to the backs of these // you can reduce the quality (and the number of polygons) by choosing a larger stepsize s (default should be 0) -(id) outlineFont: (NSString *)fontname atSize:(int)size extrude:(float)z stepsize:(int)s { CGContextRef cg_context; // we do not need to keep a cg_context: only the data needs to be kept size_t bytesPerRow; // at least (width * bitsPerComponent * number of components + 7)/8 CGColorSpaceRef colorspace; // create with CGColorSpaceCreateDevice... functions CGImageAlphaInfo alphaInfo; // see CGImage for valid enum constants size_t bitsPerComponent; // must be 1,2,4 or 8 size_t num; // number of components const char *c_fontname; int i; float maxX; int x, y; float usex, usey, usez; int STEPSIZE = 1; char jstr[2]; #ifdef TESTTIME long time; time = TickCount(); #endif if( s > 0 ) STEPSIZE = s; jstr[1] = '\0'; max_height = 8; max_width = 8; c_fontname = [fontname cString]; font = [NSFont fontWithName:fontname size:size]; [font retain]; // NSLog( @"creating glFont: actual width=%f, actual height=%f, descender=%d", // [font maximumAdvancement].width,[font defaultLineHeightForFont], (int)[font descender]); while( max_width < [font maximumAdvancement].width ) max_width += 8; while( max_height < [font defaultLineHeightForFont] ) max_height += 8; // CGContexts in grey space are all 8 bits bitsPerComponent = 8; // Gray color space is chosen here only because it is easy to convert to a bitmap colorspace = CGColorSpaceCreateDeviceGray(); // We do not need any alpha information alphaInfo = kCGImageAlphaNone; // we need to ensure that we have the correct number of componants in the colorspace ( exactly 1 ) num = CGColorSpaceGetNumberOfComponents(colorspace); // this is the lowest character in ASCII that we will want to draw (20) jstr[0] = ' '; // we now generate max_chars contiguous lists base = glGenLists( max_chars ); outline = true; // if any of the operations above have failed, we should not continue if( colorspace != nil) for( i = 0; i < max_chars ; i++ ) // each character { height[i] = max_height; width[i] = max_width ; // width* number of componants: should be 1 componant for CGColorSpaceCreateDeviceGray() bytesPerRow = width[i]*num; // must use CALLOC <- if not, memory may not be empty, leading to strange results in the drawings // space must be at least (bytesPerRow * height) data[i] = (void *) calloc( bytesPerRow, height[i] ); if( data[i] != nil ) // calloc was successful { // create the context cg_context = CGBitmapContextCreate(data[i], width[i], height[i], bitsPerComponent, bytesPerRow, colorspace, alphaInfo); if( cg_context != nil ) { // draw the current letter [self drawLetter:jstr[0] context:cg_context atx:0 withFont:c_fontname atSize:size]; x_offset[i] = (unsigned int)[font widthOfString: [NSString stringWithCString: jstr]] +1; CGContextRelease(cg_context); } else NSLog(@"NSGLFont init ERROR: Didn't create CGContext[%d]",i); } // [self getVertices:i]; //NSLog(@"For %s there are %d points", jstr, nVertices); jstr[0]++; if( nVertices >= max_vertices ) NSLog(@"too many vertices, %s", jstr); glNewList( base+i,GL_COMPILE); // compile the following glBegin( GL_QUADS ); //glBegin( GL_LINES ); maxX = 0; for( x = 0; x < max_width - STEPSIZE; x += STEPSIZE ) { for( y = 0; y < max_height - STEPSIZE; y += STEPSIZE ) { if( [self getHeight:i atX:x atY:y] == 0 && [self getHeight:i atX:x atY:y+STEPSIZE] == 0 && [self getHeight:i atX:x+STEPSIZE atY:y+STEPSIZE] == 0 && [self getHeight:i atX:x+STEPSIZE atY:y] == 0); else { usex = (float)x*xdiff; usey = (float)y*ydiff; usez = (float)[self getHeight:i atX:x atY:y]*z/255; glVertex3f(usex, usey, usez); usex = (float)x*xdiff; usey = (float)(y+STEPSIZE)*ydiff; usez = (float)[self getHeight:i atX:x atY:y+STEPSIZE]*z/255; glVertex3f(usex, usey, usez); usex = (float)(x+STEPSIZE)*xdiff; usey = (float)(y+STEPSIZE)*ydiff; usez = (float)[self getHeight:i atX:x+STEPSIZE atY:y+STEPSIZE]*z/255; glVertex3f(usex, usey, usez); usex = (float)(x+STEPSIZE)*xdiff; usey = (float)y*ydiff; usez = (float)[self getHeight:i atX:x+STEPSIZE atY:y]*z/255; glVertex3f(usex, usey, usez); } } } glEnd(); glTranslatef(x_offset[i]*xdiff, 0, 0); //NSLog(@"maxX for %s is %f", jstr, x_offset[i]*xdiff); glEndList(); } #ifdef TESTTIME NSLog(@"Outline font using height maps created in %ld ticks", TickCount() - time); #endif return self; } // if you turn TESTTIME on, you will often not see a difference between the cheapOutlineFont and the // regular outlineFont // try creating the Webdings font at 156 and you will see a difference // the cheap outline font has some problems: for instance, there is a limit to the number of polygons // that it will create (outlineFont does not have this restriction) // however, cheap outline font will almost always create fewer polygons than outlineFont // cheapOutlineFont will also have problems if you have a very large character (try Wingdings @ 250) because // certain characters will not be drawn // of course, using a large font at 250 pts is not recommended in any case, as you will be using a // HUGE number of polygons -(id) cheapOutlineFont: (NSString *)fontname atSize:(int)size extrude:(float)z { CGContextRef cg_context; // we do not need to keep a cg_context: only the data needs to be kept size_t bytesPerRow; // at least (width * bitsPerComponent * number of components + 7)/8 CGColorSpaceRef colorspace; // create with CGColorSpaceCreateDevice... functions CGImageAlphaInfo alphaInfo; // see CGImage for valid enum constants size_t bitsPerComponent; // must be 1,2,4 or 8 size_t num; // number of components const char *c_fontname; int i; int n; float maxX; char jstr[2]; #ifdef TESTTIME long time; time = TickCount(); #endif jstr[1] = '\0'; max_height = 8; max_width = 8; c_fontname = [fontname cString]; font = [NSFont fontWithName:fontname size:size]; [font retain]; outline = true; // NSLog( @"creating glFont: actual width=%f, actual height=%f, descender=%d", // [font maximumAdvancement].width,[font defaultLineHeightForFont], (int)[font descender]); while( max_width < [font maximumAdvancement].width ) max_width += 8; while( max_height < [font defaultLineHeightForFont] ) max_height += 8; // CGContexts in grey space are all 8 bits bitsPerComponent = 8; // Gray color space is chosen here only because it is easy to convert to a bitmap colorspace = CGColorSpaceCreateDeviceGray(); // We do not need any alpha information alphaInfo = kCGImageAlphaNone; // we need to ensure that we have the correct number of componants in the colorspace ( exactly 1 ) num = CGColorSpaceGetNumberOfComponents(colorspace); // this is the lowest character in ASCII that we will want to draw (20) jstr[0] = ' '; // we now generate max_chars contiguous lists base = glGenLists( max_chars ); // if any of the operations above have failed, we should not continue if( colorspace != nil) for( i = 0; i < max_chars ; i++ ) // each character { height[i] = max_height; width[i] = max_width ; // width* number of componants: should be 1 componant for CGColorSpaceCreateDeviceGray() bytesPerRow = width[i]*num; // must use CALLOC <- if not, memory may not be empty, leading to strange results in the drawings // space must be at least (bytesPerRow * height) data[i] = (void *) calloc( bytesPerRow, height[i] ); if( data[i] != nil ) // calloc was successful { // create the context cg_context = CGBitmapContextCreate(data[i], width[i], height[i], bitsPerComponent, bytesPerRow, colorspace, alphaInfo); if( cg_context != nil ) { // draw the current letter [self drawLetter:jstr[0] context:cg_context atx:0 withFont:c_fontname atSize:size]; x_offset[i] = (unsigned int)[font widthOfString: [NSString stringWithCString: jstr]] +1; CGContextRelease(cg_context); } else NSLog(@"NSGLFont init ERROR: Didn't create CGContext[%d]",i); } [self getVertices:i]; //NSLog(@"For %s there are %d points", jstr, nVertices); jstr[0]++; if( nVertices >= max_vertices ) NSLog(@"too many vertices, %s", jstr); glNewList( base+i,GL_COMPILE); // compile the following glBegin( GL_QUADS ); maxX = 0; for( n = 0; n < nVertices*4; n += 4 ) { glVertex3f(vertex[n+0]*xdiff, vertex[n+1]*ydiff, 0); glVertex3f(vertex[n+2]*xdiff, vertex[n+1]*ydiff, 0); glVertex3f(vertex[n+2]*xdiff, vertex[n+3]*ydiff, 0); glVertex3f(vertex[n+0]*xdiff, vertex[n+3]*ydiff, 0); if( z != 0 ) { glVertex3f(vertex[n+2]*xdiff, vertex[n+1]*ydiff, z); glVertex3f(vertex[n+0]*xdiff, vertex[n+1]*ydiff, z); glVertex3f(vertex[n+0]*xdiff, vertex[n+3]*ydiff, z); glVertex3f(vertex[n+2]*xdiff, vertex[n+3]*ydiff, z); glVertex3f(vertex[n+0]*xdiff, vertex[n+1]*ydiff, 0); glVertex3f(vertex[n+0]*xdiff, vertex[n+1]*ydiff, z); glVertex3f(vertex[n+2]*xdiff, vertex[n+1]*ydiff, z); glVertex3f(vertex[n+2]*xdiff, vertex[n+1]*ydiff, 0); glVertex3f(vertex[n+0]*xdiff, vertex[n+3]*ydiff, 0); glVertex3f(vertex[n+0]*xdiff, vertex[n+3]*ydiff, z); glVertex3f(vertex[n+2]*xdiff, vertex[n+3]*ydiff, z); glVertex3f(vertex[n+2]*xdiff, vertex[n+3]*ydiff, 0); glVertex3f(vertex[n+0]*xdiff, vertex[n+1]*ydiff, 0); glVertex3f(vertex[n+0]*xdiff, vertex[n+1]*ydiff, z); glVertex3f(vertex[n+0]*xdiff, vertex[n+3]*ydiff, z); glVertex3f(vertex[n+0]*xdiff, vertex[n+3]*ydiff, 0); glVertex3f(vertex[n+2]*xdiff, vertex[n+1]*ydiff, 0); glVertex3f(vertex[n+2]*xdiff, vertex[n+1]*ydiff, z); glVertex3f(vertex[n+2]*xdiff, vertex[n+3]*ydiff, z); glVertex3f(vertex[n+2]*xdiff, vertex[n+3]*ydiff, 0); } } glEnd(); glTranslatef(x_offset[i]*xdiff, 0, 0); //NSLog(@"maxX for %s is %f", jstr, x_offset[i]*xdiff); glEndList(); } #ifdef TESTTIME NSLog(@"Outline font created in %ld ticks", TickCount() - time); #endif return self; } -(id) bitmapFont: (NSString *)fontname atSize:(int)size { CGContextRef cg_context; // we do not need to keep a cg_context: only the data needs to be kept size_t bytesPerRow; // at least (width * bitsPerComponent * number of components + 7)/8 CGColorSpaceRef colorspace; // create with CGColorSpaceCreateDevice... functions CGImageAlphaInfo alphaInfo; // see CGImage for valid enum constants size_t bitsPerComponent; // must be 1,2,4 or 8 size_t num; // number of components const char *c_fontname; int i; char jstr[2]; #ifdef TESTTIME long time; time = TickCount(); #endif jstr[1] = '\0'; max_height = 8; max_width = 8; c_fontname = [fontname cString]; font = [NSFont fontWithName:fontname size:size]; [font retain]; // NSLog( @"creating glFont: actual width=%f, actual height=%f, descender=%d", // [font maximumAdvancement].width,[font defaultLineHeightForFont], (int)[font descender]); while( max_width < [font maximumAdvancement].width ) max_width += 8; while( max_height < [font defaultLineHeightForFont] ) max_height += 8; // CGContexts in grey space are all 8 bits bitsPerComponent = 8; // Gray color space is chosen here only because it is easy to convert to a bitmap colorspace = CGColorSpaceCreateDeviceGray(); // We do not need any alpha information alphaInfo = kCGImageAlphaNone; // we need to ensure that we have the correct number of componants in the colorspace ( exactly 1 ) num = CGColorSpaceGetNumberOfComponents(colorspace); // this is the lowest character in ASCII that we will want to draw (20) jstr[0] = ' '; // we now generate max_chars contiguous lists base = glGenLists( max_chars ); glPixelStorei(GL_UNPACK_ALIGNMENT, 1 ); // if any of the operations above have failed, we should not continue if( colorspace != nil) for( i = 0; i < max_chars ; i++ ) // each character { height[i] = max_height; width[i] = max_width ; // width* number of componants: should be 1 componant for CGColorSpaceCreateDeviceGray() bytesPerRow = width[i]*num; // must use CALLOC <- if not, memory may not be empty, leading to strange results in the drawings // space must be at least (bytesPerRow * height) data[i] = (void *) calloc( bytesPerRow, height[i] ); if( data[i] != nil ) // calloc was successful { // create the context cg_context = CGBitmapContextCreate(data[i], width[i], height[i], bitsPerComponent, bytesPerRow, colorspace, alphaInfo); if( cg_context != nil ) { // draw the current letter [self drawLetter:jstr[0] context:cg_context atx:0 withFont:c_fontname atSize:size]; x_offset[i] = (unsigned int)[font widthOfString: [NSString stringWithCString: jstr]] +1; CGContextRelease(cg_context); } else NSLog(@"NSGLFont init ERROR: Didn't create CGContext[%d]",i); } // create a BITMAP (as opposed to the Apple BYTEMAP CGBitmapContextRef) [self convertBitmap:i]; jstr[0]++; glNewList( base+i,GL_COMPILE); // compile the following glBitmap(width[i],height[i],0,0,x_offset[i],0,data[i]); glEndList(); } #ifdef TESTTIME NSLog(@"Bitmap font created in %ld ticks", TickCount() - time); #endif return self; } -(id) pixmapFont: (NSString *)fontname atSize:(int)size { CGContextRef cg_context; // we do not need to keep a cg_context: only the data needs to be kept size_t bytesPerRow; // at least (width * bitsPerComponent * number of components + 7)/8 CGColorSpaceRef colorspace; // create with CGColorSpaceCreateDevice... functions CGImageAlphaInfo alphaInfo; // see CGImage for valid enum constants size_t bitsPerComponent; // must be 1,2,4 or 8 size_t num; // number of components const char *c_fontname; int i; char jstr[2]; #ifdef TESTTIME long time; time = TickCount(); #endif jstr[1] = '\0'; max_height = 8; max_width = 8; c_fontname = [fontname cString]; font = [NSFont fontWithName:fontname size:size]; [font retain]; // NSLog( @"creating glFont: actual width=%f, actual height=%f, descender=%d", // [font maximumAdvancement].width,[font defaultLineHeightForFont], (int)[font descender]); while( max_width < [font maximumAdvancement].width ) max_width += 8; while( max_height < [font defaultLineHeightForFont] ) max_height += 8; // CGContexts in grey space are all 8 bits bitsPerComponent = 8; // Gray color space is chosen here only because it is easy to convert to a bitmap colorspace = CGColorSpaceCreateDeviceRGB(); alphaInfo = kCGImageAlphaPremultipliedLast; // we need to ensure that we have the correct number of componants in the colorspace ( exactly 1 ) num = CGColorSpaceGetNumberOfComponents(colorspace) + 1; // this is the lowest character in ASCII that we will want to draw (20) jstr[0] = ' '; // we now generate max_chars contiguous lists base = glGenLists( max_chars ); glPixelStorei(GL_UNPACK_ALIGNMENT, 1 ); pixelFont = true; // if any of the operations above have failed, we should not continue if( colorspace != nil) for( i = 0; i < max_chars ; i++ ) // each character { height[i] = max_height; width[i] = max_width ; // width* number of componants: should be 1 componant for CGColorSpaceCreateDeviceGray() bytesPerRow = width[i]*num; // must use CALLOC <- if not, memory may not be empty, leading to strange results in the drawings // space must be at least (bytesPerRow * height) data[i] = (void *) calloc( bytesPerRow, height[i] ); if( data[i] != nil ) // calloc was successful { // create the context cg_context = CGBitmapContextCreate(data[i], width[i], height[i], bitsPerComponent, bytesPerRow, colorspace, alphaInfo); if( cg_context != nil ) { // draw the current letter [self drawLetter:jstr[0] context:cg_context atx:0 withFont:c_fontname atSize:size]; x_offset[i] = (unsigned int)[font widthOfString: [NSString stringWithCString: jstr]] +1; CGContextRelease(cg_context); } else NSLog(@"NSGLFont init ERROR: Didn't create CGContext[%d]",i); } // create a BITMAP (as opposed to the Apple BYTEMAP CGBitmapContextRef) jstr[0]++; glNewList( base+i,GL_COMPILE); // compile the following glDrawPixels(width[i],height[i],GL_RGBA,GL_UNSIGNED_BYTE,data[i]); glBitmap(0,0,0,0,x_offset[i],0,NULL); glEndList(); } #ifdef TESTTIME NSLog(@"Pixmap font created in %ld ticks", TickCount() - time); #endif return self; } -(void) drawLetter:(char) ch context: (CGContextRef)cg_context atx:(int)xpos withFont:(const char *)fontname atSize:(int)size { // OpenGL will draw these upside down if we don't do this CGContextTranslateCTM( cg_context, 0, max_height ); CGContextScaleCTM( cg_context, 1.0,-1.0); // set the gray fill color to 1,1 which will make the bitmap work properly CGContextSetGrayFillColor(cg_context, 1.0, 1.0); // if you're doing a glDrawPixel, then leave this on // using glBitmap means converting to a bit image, and antialiasing that doesn't really work if( !outline && !pixelFont ) CGContextSetShouldAntialias(cg_context, 0 ); // some fonts don't work very well, like "Courier" // they are drawn very strangely CGContextSelectFont(cg_context, fontname, size, kCGEncodingMacRoman ); CGContextSetTextDrawingMode(cg_context, kCGTextFill); // draw exactly one glyph into the context CGContextShowTextAtPoint(cg_context, xpos, -[font descender], &ch, 1); } // clean up the memory -(void) dealloc { int i; for( i=0; i width[number] || y > height[number] || x < 0 || y < 0) return 0; else return (int)olddata[i]; } - (void) getVertices: (int) number { int row, column, i; int vertexIndex; unsigned char *olddata = data[number]; bool inrow = false; nVertices = 0; vertexIndex = 0; for( row = 0; row < height[number] && nVertices < max_vertices ; row+=outlinehelp ) { for( column = 0; column < width[number] && nVertices < max_vertices ; column++ ) { i = row * width[number] + column; if( olddata[i] != 0 && !inrow ) { inrow = true; vertex[vertexIndex+0] = column; vertex[vertexIndex+1] = row; } if( olddata[i] == 0 && inrow ) { vertex[vertexIndex+2] = column; vertex[vertexIndex+3] = row+outlinehelp; inrow = false; nVertices++; vertexIndex = nVertices * 4; } } if( inrow ) { vertex[vertexIndex+2] = column; vertex[vertexIndex+3] = row+outlinehelp; inrow = false; nVertices++; vertexIndex = nVertices * 4; } } } - (void) setTextured: (bool) t { textured = t; } - (bool) outline { return outline; } - (bool) textured { return textured; } @end // write to the screen using font f at location x,y with the specified string void glPrint( NSGLFont *f, float x, float y, const char *fmt, ... ) { char text[256]; va_list ap; NSString *s; GLboolean tex2d, light; if( fmt == NULL ) return; // format the string into (char *) text va_start( ap, fmt ); vsprintf( text, fmt, ap ); va_end(ap); if( [f textured] ) { s = [NSString stringWithCString: text]; [f writeString: s ]; } else { // I always get a SIGSEGV 11 when glRasterPos* is called with textures off and lighting on // so here I turn them off. glGetBooleanv( GL_TEXTURE_2D, &tex2d ); glDisable(GL_TEXTURE_2D); glGetBooleanv( GL_LIGHTING, &light ); glDisable(GL_LIGHTING); // RasterPos to the correct location glRasterPos2f(x, y); // create an NSString s = [NSString stringWithCString: text]; // write the string [f writeString: s ]; // enable as required if( tex2d ) glEnable(GL_TEXTURE_2D); if( light ) glEnable(GL_LIGHTING); } } void glPrint3( NSGLFont *f, float x, float y, float z, const char *fmt, ... ) { char text[256]; va_list ap; NSString *s; GLboolean tex2d, light; if( fmt == NULL ) return; // format the string into (char *) text va_start( ap, fmt ); vsprintf( text, fmt, ap ); va_end(ap); if( [f textured] ) { s = [NSString stringWithCString: text]; [f writeString: s ]; } else { // I always get a SIGSEGV 11 when glRasterPos* is called with textures off and lighting on // so here I turn them off. glGetBooleanv( GL_TEXTURE_2D, &tex2d ); glDisable(GL_TEXTURE_2D); glGetBooleanv( GL_LIGHTING, &light ); glDisable(GL_LIGHTING); // RasterPos to the correct location glRasterPos3f(x, y, z); // create an NSString s = [NSString stringWithCString: text]; // write the string [f writeString: s ]; // enable as required if( tex2d ) glEnable(GL_TEXTURE_2D); if( light ) glEnable(GL_LIGHTING); } }