|
by Michael Powers
June 2004
Read Part 1: A Terminal Emulator for MIDP
Read Part 2: Advanced Terminal Emulation for MIDP
Download the project code
In Part 1 of this series we explored MIDP's potential for networked applications by building a terminal emulator. Part 2
focused on building a proper user interface to make MIDTerm a decently
interactive application. In Part 3, we'll add some polish -- and
some badly needed control over appearance -- by taking advantage
of MIDP's capabilities for rendering custom fonts.
First we'll examine the advantages and constraints of the built-in
support for fonts, and determine what kinds of applications might
require custom fonts. Then I'll show you a technique for implementing
your own fonts, and we'll put it to good use in the terminal emulator
MIDlet.
Built-In Fonts
You can draw text with MIDP's Graphics class by calling its setFont() to specify a font, then calling one of that font's drawChar() or drawString()
methods to render your text onto a canvas or onto an off-screen image.
MIDP affords you only a limited set of font options, however: The font type can be monospaced or proportional, the size can be small, medium, or large, and the style
can be plain or any combination of bold, italic, and underlined. The
runtime implementation may not have a font that meets your criteria,
and it's free to return any font it determines to be a close enough
approximation.
Most applications can live with these limitations. Their developers
want them to blend in with the look and feel of the host platform
anyway. To make doing so easier, MIDP 2.0 provides a new getFont()
method that can give you the font commonly used for drawing static
text, like titles and labels, and the one used for drawing characters
the user enters in text fields. You can be confident these fonts exist
and that they are appropriately sized and consistent with the rest of
the application. As long as you measure the height, width, and baseline
of the fonts you are given and adjust your user interface appropriately
at runtime, your application should run well on many different types of
devices.
Some applications, though, need more control over how they appear.
For example, most games want a typeface that is consistent in theme
with the rest of their graphics. Some platforms may present specific
problems: Requesting a monospaced font might return a proportional
font, the smallest font may be too big, and some fonts may be
unattractive or even illegible. These differences multiply the
difficulties in creating and maintaining cross-platform software.
MIDTerm is just such a case. We need to be sure that its font is
monospaced, small enough to maximize the number of rows and columns on
the screen, and easily legible, but at this point the canvas that
displays the terminal content is at the mercy of the device
implementation. If the given font is too large the terminal becomes
unusable, and if it isn't monospaced the application doesn't work at
all. At this point in our project, the font that the MIDP emulator
returns is monospaced and appropriately small, but it isn't
particularly easy to read.
Implementing a custom font will give us the control we need to have
confidence in MIDterm's usability. Equally important, its performance
will be more predictable across all MIDP devices because it will no
longer depend on each platform's native font implementation.
When it's done, you'll be able to see the improvement for yourself:
Before: Built-in Font |
After: Custom Font |
 |
 |
Creating A Custom Font
Fonts come in two varieties:
- Outline fonts contain the instructions needed to draw each
of the characters. These instructions are precise, lengthy, and
complex, but they allow the operating system to render the font at any
size and in any style.
- Bitmap fonts tell the operating system only which pixels are
on and which are off for each character. They're simple and concise,
but they need a separate set of instructions for each supported size
and style of the font.
Mobile devices have such limited processing power that outline fonts
are out of the question. Natively, these devices use bitmap fonts, and
that's what we'll use as well. The operating system won't render our
implementation as quickly as a native font, but that is an acceptable
sacrifice to gain absolute control over the appearance and layout of
our font.
The drawing instructions for a bitmap font are literally a mapping
of bits to a location in a graphics context. Each character in a bitmap
font corresponds to an arrangement of pixels that is copied from memory
to the screen. Said another way, each character is represented by an
off-screen image that gets copied to the screen. Looking at the font
this way immediately suggests that the font definition -- the file
that contains the instructions for rendering the fonts -- can
itself be an image file in a compressed format like PNG or JPEG. We can
read this image into memory, then copy portions of it to screen
whenever we want to draw characters in our font.
The first step then is to create this image. On your desktop
computer, use your favorite image editor to create a new document. This
document should be as tall as you want your font to be, and wide enough
to hold each of the characters in the character set you want to render.
How wide is that?
Java technology generally uses the Unicode Character Standard.
The Unicode character set has over 95,000 characters -- more than
we can afford, and far more than we need! -- so MIDTerm's
implementation of the Telnet protocol is based on the 7-bit ASCII
character set, which contains only 128 characters. Because MIDTerm
needs a monospaced font, the width of the document will be 128 times
the width of one character. For instance, if each character is 12
pixels high and 10 pixels wide, to hold all 128 characters you'll need
an image that's 12 high and 1,280 wide.
You now have 128 places to put characters that correspond to ASCII
codes 0 through 127. To find out which characters to place in which
slots, you may need an ASCII chart. Note that the first 31 characters are "control characters" and are not meant to be seen, so you can just leave them blank.
You can plot each of the characters into its slot by hand,
pixel-by-pixel, but it's much easier to use characters from one of the
fonts on your desktop computer. Just make sure it's a font you either
own or have a license to use. I used a font called Anonymous, which has
a very legible 6-point rendition and is free to use. You can download
it from Mark Simonson Studios.
Using Anonymous, my font image file looks like this:

By definition a bitmap font uses only two colors because a bit has
only two states: on and off. You should therefore use one color to
define the pixels that are "on," and a different color for all the
rest. Because MIDP 2.0 devices are required to support images with
transparency, you should have your image editor mark the "off" color as
transparent. Transparent pixels are invisible, allowing whatever pixels are already on your application's canvas to show through.
You'll want to save the image file in PNG format. A one-bit image
with many contiguous regions of uniform color is exactly the kind of
image for which the basic PNG compression algorithm was designed. Your
font image file will be very compact: the PNG file for Anonymous is
only 803 bytes in size.
The Custom Font Class
Now we need to write some software that can read the image file and render portions of it on screen, in a new class named CustomFont.
To make CustomFont easy to learn and use, it should look and feel as much like the standard MIDP Font class as possible. We can't subclass Font, because it has been declared final, to allow implementers to optimize using native code. We can, however, mimic its public interface. Every method of Font is also present in CustomFont, and only the static factory method getFont() has a different signature, taking the name of a file instead of a preferred font type:
public static CustomFont getFont(
String inName, int inStyle, int inSize );
|
In many MIDP applications we'd pass a font to the Graphics object by calling its setFont() method, then invoke that object's drawChars() and drawString() methods to render the text. We can't follow that pattern here, however. Because CustomFont isn't a subclass of Font, we cannot pass it to Graphics.setFont(). Instead, CustomFont provides public methods for drawing characters that mimic methods of the Graphics class, each taking the current Graphics instance as its first argument.
public void drawChar(
Graphics g, char character,
int x, int y, int anchor );
public void drawChars(
Graphics g, char[] data,
int offset, int length,
int x, int y, int anchor );
public void drawString(
Graphics g, String str,
int x, int y, int anchor );
public void drawSubstring(
Graphics g, String str,
int offset, int length,
int x, int y, int anchor );
|
Applications that use CustomFont will need to modify
their drawing code to call these methods of the custom font class,
rather than the corresponding methods of the Graphics object.
Now let's take a look at how the character bitmaps get to the screen. There are two approaches to consider.
The obvious approach is to take the source image and break it up into 128 images, one for each character, as shown here:
...
images = new Image[ 128 ];
for ( int i = 0; i < 128; i++ )
{
images[i] = Image.createImage(
image, i*width, 0, width, height, 0 );
}
...
|
Images created from an external resource are considered immutable.
Because the pixels cannot change, the runtime can optimize memory
allocation. Images we create from an immutable image with the createImage()
method are immutable too. So, while we have 128 small images and one
large image, we can hope that the runtime will be smart enough to
notice that none of them can be modified, and keep only one copy of our
bitmap in memory. Even if it does, however, the sheer overhead of 128
image instances may prove too much for a small device to handle.
An alternate approach is to have only one large image and use it like an IBM Selectric typewriter's typeball.
Revolutionary in its day, the typeball replaced individual typebars for
each letter with raised characters arranged on a spherical surface. The
ball swiveled and tilted for each keystroke, and enabled the Selectric
to achieve then-dazzling speed (almost 15 characters a second!) The
typeball may be long obsolete, but we can borrow the idea behind it to
make MIDTerm more efficient. To draw a character, we'll clip
the graphics context, and shift the image left or right so that only
the character we want is visible in the clipped area, then draw the
image. Every Graphics instance maintains a clip region
at all times, and only commands that affect the clip region are
actually executed. In this case, we can count on the runtime to draw
only those pixels that fit into the clip region. This approach is
demonstrated here:
public void drawChar(
Graphics g, char character, int x, int y, int anchor )
{
int clipX = g.getClipX();
int clipY = g.getClipY();
int clipW = g.getClipWidth();
int clipH = g.getClipHeight();
g.setClip( x, y, width, height );
g.drawImage(
image, x - width*character, y, anchor );
g.setClip( clipX, clipY, clipW, clipH );
}
|
The downside to this approach is that we have to remember the existing clip region before we change it, so we can return the Graphics
instance to the state in which we found it. The clip region must be
preserved in each call to one of the draw methods, at a significant
cost: We must make four method calls and allocate four ints each time.
We're stuck with a classic time-space tradeoff: To save memory CustomFont
consumes more processor time, using clipping instead of allocating
individual image instances for each character. On devices with little
memory, it's better to run slowly than not at all.
One little chore CustomFont must do when initializing is to calculate the font's baseline.
Along with its height and the widths of its characters, a font's
baseline is another important determinant in font rendering because it
defines where a font's characters "sit" on a line of text. If you have
different fonts on the same line of text, you need to align their
baselines to make them appear at the same level.

We could change CustomFont.getFont() to require the
caller to specify the baseline for its chosen font, but there's a
better way. MIDP 2.0 makes it possible to detect the baseline
dynamically. The getRGB() method gives us direct access
to the individual pixels of an image, and a simple heuristic can
determine which row of pixels is the baseline.
...
// determine background color: assume it's at 0, 0
image.getRGB( row, 0, 1, 0, 0, 1, 1 );
background = row[0];
// here's the heuristic: find the row on the bottom
// half of the image with the most non-background pixels
for ( int y = height/2; y < height; y++ )
{
total = 0;
image.getRGB( row, 0, imageWidth, 0, y, imageWidth, 1 );
for ( int x = 0; x < imageWidth; x++ )
{
if ( row[x] != background ) total++;
}
if ( total > max )
{
max = total;
result = y;
}
}
...
|
CustomFont assumes the top-left pixel in the image is
in the background color. It counts the non-background pixels in each
row in the bottom half of the image, and judges the row with the most
foreground pixels to be the baseline. Determining the baseline at
runtime gives you one less thing to worry about when creating custom
font bitmaps.
Dynamic Styling
The signatures of Font.getFont() and CustomFont.getFont()
differ in their first argument -- the standard method takes a font
type, and ours takes the name of an image file -- but both accept
size and style arguments. CustomFont ignores the
requested font size, because having a separate image in memory for each
font size would be too expensive, and attempting to scale a bitmap
larger or smaller at runtime would produce results that were less than
legible. Our custom font class does try to respect the requested style,
however, by employing some simple but effective techniques that alter
how it draws the character images at runtime.
Easiest is underlining. We just draw a simple line under the character, two pixels below the baseline.
...
if ( ( style & Font.STYLE_UNDERLINED ) != 0 )
{
g.drawLine(
x, y + baseline + 2, x + width, y + baseline + 2 );
}
...
|
Bolding is only slightly more complicated. To make a character bold, CustomFont
draws the character an extra time, one pixel to the right. Because the
background is transparent, the two sets of pixels overlap to make the
character appear darker.
...
if ( ( style & Font.STYLE_BOLD ) != 0 )
{
// draw an additional time, one pixel to the right
g.drawImage(
image, x - width*character + 1, y, anchor );
}
...
|
To make a character italic, CustomFont uses clipping to draw the top half of the character shifted one pixel to the right. The result is surprisingly good.
...
if ( ( style & Font.STYLE_ITALIC ) != 0 )
{
g.setClip( x + 1, y, width, height/2 );
g.drawImage(
image, x - width*character + 1, y, anchor );
g.setClip( x, y+height/2, width, height/2 );
g.drawImage(
image, x - width*character, y, anchor );
}
...
|
Here's how these styles look in practice:
STYLE_PLAIN
|
STYLE_UNDERLINED
|
 |
 |
STYLE_BOLD
|
STYLE_ITALIC
|
 |
 |
CustomFont has a more difficult time drawing characters in specific colors. While the Graphics class renders a font in the current color, CustomFont
can only copy the pixels from the full font image, so the characters
appear in whatever non-transparent colors were used in the original
image.
MIDP 2.0 does provide a way to create a new image from the pixels returned by getRGB().
You can copy the pixels for a character, scan through and change the
color of every non-background pixel, create a new image, and paint that
image to the screen -- but this tactic is processor-intensive and
perceptibly slow. If you know in advance what color you'll need, you
should use that color in the foreground when creating your font.
MIDTerm uses a black background, so the full font image contains white
characters against a transparent background.
Putting the Custom Font to Work
Because CustomFont conforms so closely to MIDP's Font class, MIDTerm's TelnetCanvas class needs very few changes to reap the advantages of a custom font.
| Using MIDP Fonts |
Using Custom Font |
font = Font.getFont(
Font.FACE_MONOSPACE,
Font.STYLE_SMALL,
Font.SIZE_PLAIN ); |
font = CustomFont.getFont(
"/mono.png", // the font bitmap file
Font.SIZE_SMALL, // ignored
Font.STYLE_PLAIN ); // no styling |
g.setFont( font );
g.drawChar( (char) b,
insetX + x*fontWidth,
insetY + y*fontHeight,
g.TOP | g.LEFT ); |
font.drawChar( g, (char) b,
insetX + x*fontWidth,
insetY + y*fontHeight,
g.TOP | g.LEFT );
|
With these few changes, the application runs as before, only better:
more legibly, if slightly more slowly, and more predictably on varying
MIDP implementations. You can use CustomFont likewise in other applications with little or no modification.
Summary
We've employed a simple but effective technique to give MIDP
applications absolute control over the drawing of text. You can create
your own fonts with a simple image editor and, using the graphics and
imaging capabilities of MIDP 2.0, you can render your font on screen
with acceptable performance. You can style your font however you wish,
even dynamically at runtime. For MIDTerm, a custom font improves
legibility and usability and reduces the effort needed to ensure that
the application runs smoothly on multiple platforms.
Reference
The links in this article are listed again here.
About the Author: Michael Powers
is Principal of mpowers LLC, a software consultancy for desktop and
wireless platforms, and he has been working with Java technology in its
various incarnations since its inception. His award-winning Piranha
Pricecheck MIDlet is taking the world by storm, and is a free download
from mpowers.net.
Back To Top
|
|