Rather than demonstrating text rendering with
yet another goofy toy application, let’s do something useful for a change.
Overlaying a frames-per-second counter in one corner of the iPhone screen
provides a quick and easy way of evaluating graphics performance; see
Figure 1.
Before writing any application code, you’d need
to generate an image that contains bitmaps for the numbers zero through
nine, as depicted in Figure 2. (Don’t bother trying
to create this yet; you’ll see a way to automate this shortly.)
You probably already guessed that you need to
store off the bounding box of each glyph in order to compute the
appropriate texture coordinates. Thinking about this a bit more, you’ll
realize a mere bounding box is not enough. When you’re writing a sentence
on ruled paper, some parts of letters extend below the baseline, like the
descender of the lowercase p. And, in the case of the
rather artsy font shown in Figure 3, the
type designer wants the 9 numeral to be vertically
offset from the other letters. Further complicating matters is the fact
that the bounding boxes of glyphs can overlap in the destination image. In
Figure 3, observe how the descender of the
letter p extends right below the letter
i.
It turns out that associating a specific set of
glyph metrics with each character supplies enough
information to achieve the simple text layout shown in Figure 3. A popular naming convention for these
metrics is described in Figure 4; in this
diagram, the origin represents the current pen position.
To summarize, the four glyph metrics are as
follows:
Bearing vector
2D vector describing the offset from the
pen position.
Advance vector
2D vector describing how to advance the
pen to the next position after rendering the current glyph. The y
component is always zero for Latin-based alphabets.
Width
The horizontal length of the
glyph.
Height
The vertical length of the glyph.
Using these metrics, Example 1 the pseudocode for a simple text layout
algorithm.
Example 1. Simple text layout algorithm
void RenderText(string s, vec2 p) { for each character c in s { metrics m = GlyphTable[c].Metrics vec2 topLeft = GlyphTable[c].Position box source = box(topLeft, m.Width, m.Height) box dest = box(p + m.BearingVector, m.Width, m.Height) Blit(source, dest) p += m.AdvanceVector } }
|
1. Generating a Glyphs Texture with Python
Before writing any application code, we need
to choose a way of generating a glyphs texture and a set of metrics for
each glyph.
Leveraging Quartz is perhaps the most obvious
way of generating a glyphs texture . This can be done at runtime
when your application first starts up. This might slow down your startup
time by a tiny amount, but it has the benefit of shrinking the size of
the application bundle.
My preference is to generate the glyphs
texture as a build step, mostly because it simplifies my application
code. Build steps take place in Xcode rather than the iPhone execution
environment, which brings a much richer tool set to the table. This is a
perfect use case for a scripting language, and Python comes to mind
first.
Note:
There are many ways of
generating a glyphs texture; here I’m giving an overview of my
favorite. Take it only as a high-level example.
Given that we’re using Python in a build
step, we need to find some useful Python modules for image manipulation
and image generation. At the time of this writing, the Python
Imaging Library (PIL) is the most popular imaging
manipulation module, and it provides excellent support for manipulating
PNG images at a low level. However, it’s not quite enough on its own
because it doesn’t provide direct access to the glyph metrics that we
need. Another popular library is Cairo, which has
a well-maintained Python binding called pycairo.
Cairo is robust and fast, and it’s used as the rendering backend in
Firefox, Mono, and GTK. So, let’s go with PIL (http://www.pythonware.com/products/pil/) and pycairo
(http://www.cairographics.org/pycairo/).
The copy of Python that’s installed on Mac
OS X won’t have these modules installed by default, so you’ll have to
do a little bit of prep to install them. First, install the Python Imaging Library.
Go here to download the source: http://www.pythonware.com/products/pil/. At the time of
this writing, the 1.1.7 version of PIL was the most recent and worked
well. Make sure you download the version for Python 2.6 to ensure that
the script will work. Extract the tarball (you can double-click it to
extract it), open the Terminal, and cd to the
top-level directory of the source code distribution. Next, execute the
following command: sudo python setup.py install
Next, install pycairo. Download the source
from here: http://www.cairographics.org/ (the
script was tested with version 1.8.8). Extract the tarball, open the
Terminal, and cd to the top-level directory of the
source code distribution. Next, execute the following commands: ./configure \ --prefix=/System/Library/Frameworks/Python.framework/Versions/2.6 make sudo make install
You’ll also need to install Cairo itself
since pycairo is only a thin wrapper; build instructions and downloads
are available from http://www.cairographics.org/download/. |
Rather than packaging the glyphs texture as a
PNG or PVR file, let’s serialize the data to a C header file. Since it’s
a single-channel texture, the amount of data is relatively small. The
header file will also provide a convenient place to store the glyph
metrics. We’ll simply have our Python script spawn
PVRTexTool for generating the header file from the
image. We’ll still generate a PNG file for preview purposes, but we
won’t include it in the application bundle. See Example 7-2 for the complete Python script that
generates Figure 2.
Example 2. CreateNumerals.py
import cairo import os from PIL import Image
# Create a Cairo image surface: imagesize = (256,32) surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, *imagesize) cr = cairo.Context(surface) padding = 3
# Choose a font (look in /Library/Fonts) and set up the transforms. cr.select_font_face("Apple Chancery", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD) cr.set_font_size(32) cr.set_source_rgb(1,1,1)
# Create a string for appending the glyph metrics to the texture file: glyphs = ''' struct GlyphPosition { int X; int Y; };\n struct GlyphMetrics { int XBearing; int YBearing; int Width; int Height; int XAdvance; int YAdvance; };\n struct Glyph { GlyphPosition Position; GlyphMetrics Metrics; };\n static const Glyph NumeralGlyphs[] = {\n'''
# Render glyphs '0' through '9' and write out their extents: x, y = 0, 0 for character in '0123456789': extents = cr.text_extents(character) x_bearing, y_bearing, width, height, x_advance, y_advance = extents glyphs += ' {{ %d, %d }, ' % (x, y) glyphs += '{ %d, %d, %d, %d, %d, %d }},\n' % extents cr.save() cr.translate(x, -y_bearing) cr.text_path(character) cr.fill() cr.restore() x += width + padding glyphs += '};\n'
# Extract the alpha channel and open it up for a quick preview: surface.write_to_png("NumeralsTexture.png") image = Image.open("NumeralsTexture.png") image.load() image.split()[3].save("NumeralsTexture.png") os.system("open NumeralsTexture.png")
# Serialize the image data to a C header file: os.system('PVRTexTool -h -yflip1 -fOGL8 -iNumeralsTexture.png')
# Write to the header file: headerFile = open('NumeralsTexture.h', 'a') headerFile.write(glyphs) headerFile.close()
|
Note:
For this to work, you must either put the
location of PVRTexTool into your shell’s PATH environment
variable or copy PVRTexTool into one of your PATH
entries, such as /usr/local/bin. If you’ve
extracted the Khronos SDK into your current directory, you could copy
the file and mark it executable with these commands:
cd SDKPackage/Utilities/PVRTexTool/PVRTexToolCL/MacOS
sudo cp PVRTexTool /usr/local/bin
sudo chmod +x /usr/local/bin/PVRTexTool
cd -