In the last few chapters, we pulled all of our OpenGL shader and program management code out of the view controller and created a new EDOpenGLProgram class to take its place. Using our new class, we can now easily and dynamically create OpenGL programs with custom shaders and variable attributes and uniforms. We modified our view controller to take advantage of the new class, and next we’ll be creating a font importer and text rendering system that will use it as well.
OpenGL ES 2.0 on the iPhone is great, but there’s no built-in support for rendering text strings into your OpenGL space. Even simple tasks like displaying “Game Over” in the middle of the screen, or maintaining a score counter somewhere will involve loading textures, rendering triangles, and getting your shaders involved.
In order to develop a solution to this problem, we have to think about how OpenGL will be handling our text strings, and how much control we want over them. We know that loading and managing textures are expensive operations in OpenGL, so we’ll probably want to make one texture to hold all of our letters, and use texture coordinates to display the ones we need. We also know that we’ll need something to render those texture-based letters on, so we’ll need a mechanism to construct triangle vertices based on the length of our strings.
When I originally approached this problem, I used Photoshop Elements to create a font sheet by simply typing the alphabet in upper case and lower case, all of the numbers, and most of the symbols on my keyboard. I then created a layer of guidelines to identify the upper left X and Y coordinates, and the lower right X and Y coordinates of each letter and symbol. Knowing where the two corners were for each letter, and using those coordinates to calculate the height and width of each letter, I was able to create a class that would take simple text strings and generate triangle strips with the proper texture bits on them.
My first font sheet, with the guidelines layer enabled, looked something like this.
Of course, the guidelines layer was disabled when I saved it at a .png file for use as a texture.
Even though it took hours to identify and record each upper-left and lower-right X and Y coordinate pair, I was pretty proud of my new class when I was able to write out any string I wanted perfectly into my OpenGL space.
That lasted until I realized that I had forgotten a few characters, and I had to reload the file in Photoshop Elements, type in the forgotten symbols, draw the guidelines properly, and use the mouse pointer to get the missing coordinates. After that, I had to update the header file I had created to hold all of this information. As painful as that had been, I realized that it would be much worse if I decided to change the font altogether.
Since I had proven that I could make the program logic work with the right texture and character coordinates, the next logical step was make a class that could do all of the mapping work for me, and pass it into my new text string render code. The new class would need to be able to read in the image file, scan the pixel rows to determine the upper and lower boundaries of each row, then work within those rows to pull out each individual letter and symbol. The new class would also need to provide that coordinate information and an index to the class that was responsible for rendering the text.
The first problem I ran into was the variable heights of the letters. Scanning a row of lowercase letters would yield a significantly shorter height than scanning a row of uppercase letters. I needed some way to set the upper and lower boundaries of the entire font set, not just the current row.
I ended up solving the problem by requiring that the first two characters of each row include a symbol that went as far below the baseline as any character in the font, and another that ascended as high as any character in the font. Effectively, the first two characters would force the pixel scanner to detect a consistent top and bottom boundary for every row. Since the highest and lowest characters can vary from font to font, it’s up to the font sheet creator to use the appropriate leading characters in every row.
Here’s a font sheet that follows my new specifications for the Cooper Std font from Photoshop Elements.
For this font, the open bracket and vertical bar ended up being the highest and lowest extending characters of the set.
Another sheet I created for the Chalkduster font ended up looking like this.
For this font, the lowest extending character was a lowercase q, and one of the tallest characters was an open bracket.
Either of these sheets works fine in the font importer class, and ends up being rendered correctly in OpenGL by the text string management classes. The key is setting the proper row boundaries with the first two characters, which will allow our font importer class to generate a consistent font height for all characters on the sheet, regardless of which row they appear in or how tall they are individually.
Now that we have a good idea of how to format our font sheets, let’s think through what our font importer class is going to need to do.
First, the font importer will need to be able to read in the graphics file and inspect it at a very low level, reading the color data per bit. In order for the font importer to properly detect which pixels might be character data and which pixels might be the background color, we need to set a threshold. So let’s add this to our font sheet spec: letters should be black and the background should be white. If the pixel scanner hits a pixel that isn’t white, it will be considered part of a letter. If we’re scanning rows, that pixel will denote the start of a row of letters.
But how do we detect pixel colors? We’ll be reading our graphics file as a series of pixels represented as RGBA data. RGBA stands for Red, Green, Blue, Alpha, and is the format of the data that makes up a pixel in our file. The higher the value, the brighter the intensity of that associated color. The alpha value is how opaque that pixel should be, with higher values indicating a higher opacity. An alpha value of 0 is transparent, or invisible, while an alpha value of 255 is completely opaque.
For the RGB part of RGBA, we’ll be using three values of 0 through 255, so black would be 0,0,0, or all colors turned off. Bright green would be 0,255,0, or red off, green all the way on, and blue off.
Knowing this, we can tell if a pixel is white by looking for a pixel color value of 255,255,255. However, we’ll allow for slight variations, so we’ll say any pixel equal to or brighter than 250,250,250 is white, and anything darker is a letter part.
Once the pixel scanning code can reliably detect where letters start and end, it’s just a matter of recording all of that information and presenting it in a way that our text rendering class can make use of.
We’ll start building the EDFontImporter class in the next chapter.