Using Custom Fonts or Bitmap Fonts Part II

Jump to part: 1 | 2


On the previous part of this tutorial, we created a clsFont class for drawing bitmap fonts. Now we will use the class to see if it actualy works. So if you haven't read the first part of this tutorial just click on this link: Using Custom Fonts or Bitmap Fonts Part I. I have also added more methods to the clsFont class that you may find useful so stick around a bit.


Using the Bitmap Font Writer
Open the clsCanvas code so we can begin. We will start by adding a new global variable to hold an instance of the clsFont class. Let's call it "myFont" and add it under the other variable declarations like so:


private midMain fParent;

private clsFont myFont;

/** Creates a new instance of clsCanvas */
public clsCanvas(midMain m) {



If you expand the images folder on the Projects panel, you should see the image file "fonts.png" listed there. This is the bitmap font we're going to pass to the clsFont.load() method. Add these lines in the end of the clsCanvas.load():


}

myFont = new clsFont();
myFont.load("/images/fonts.png");

}



We will also call the clsFont.unload() method inside the clsCanvas.unload() method like so:


public void unload(){
// make sure the object get's destroyed

myFont.unload();
myFont = null;

}



Lastly, we will use the clsFont.drawString() method to draw some text on the screen. Add these lines above the call to flushGraphics() in the main loop:


g.fillRect(0, 0, screenW, screenH);

myFont.drawString(g, "Hello Neo...", 10, 50);
myFont.drawString(g, "1234567890", 10, 70);
myFont.drawString(g, "ABCDEFG abcdefg", 10, 90);

flushGraphics();



Test your MIDlet by pressing F6 on your keyboard and you should see something like this:

Bitmap Font on WTK 2.5.1

I almost forgot to post the completed clsCanvas source code:


package MyGame;

import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.game.GameCanvas;

public class clsCanvas extends GameCanvas implements Runnable {

// key repeat rate in milliseconds
public static final int keyDelay = 250;

//key constants
public static final int upKey = 0;
public static final int leftKey = 1;
public static final int downKey = 2;
public static final int rightKey = 3;
public static final int fireKey = 4;

//key states for up, left, down, right, and fire key
private boolean[] isDown = {
false, false, false, false, false
};

//last time the key changed state
private long[] keyTick = {
0, 0, 0, 0, 0
};

//lookup table for key constants :P
private int[] keyValue = {
GameCanvas.UP_PRESSED, GameCanvas.LEFT_PRESSED,
GameCanvas.DOWN_PRESSED, GameCanvas.RIGHT_PRESSED,
GameCanvas.FIRE_PRESSED
};

private boolean isRunning = true;
private Graphics g;
private midMain fParent;

private clsFont myFont;

/** Creates a new instance of clsCanvas */
public clsCanvas(midMain m) {
super(true);
fParent = m;
setFullScreenMode(true);
}

public void start(){
Thread runner = new Thread(this);
runner.start();
}

public void load(){
try{
// load the images here

}catch(Exception ex){
// exit the app if it fails to load the image
isRunning = false;
return;
}

myFont = new clsFont();
myFont.load("/images/fonts.png");

}

public void unload(){
// make sure the object gets destroyed

myFont.unload();
myFont = null;

}

public void checkKeys(int iKey, long currTick){
long elapsedTick = 0;
//loop through the keys
for (int i = 0; i < 5; i++){
// by default, key not pressed by user
isDown[i] = false;
// is user pressing the key
if ((iKey & keyValue[i]) != 0){
elapsedTick = currTick - keyTick[i];
//is it time to toggle key state?
if (elapsedTick >= keyDelay){
// save the current time
keyTick[i] = currTick;
// toggle the state to down or pressed
isDown[i] = true;
}
}
}
}

public void run() {
int iKey = 0;
int screenW = getWidth();
int screenH = getHeight();
long lCurrTick = 0; // current system time in milliseconds;

load();
g = getGraphics();
while(isRunning){

lCurrTick = System.currentTimeMillis();
iKey = getKeyStates();

checkKeys(iKey, lCurrTick);

if (isDown[fireKey]){
isRunning = false;
}

//restore the clipping rectangle to full screen
g.setClip(0, 0, screenW, screenH);
//set drawing color to black
g.setColor(0x000000);
//fill the screen with blackness
g.fillRect(0, 0, screenW, screenH);

myFont.drawString(g, "Hello Neo...", 10, 50);
myFont.drawString(g, "1234567890", 10, 70);
myFont.drawString(g, "ABCDEFG abcdefg", 10, 90);

flushGraphics();

try{
Thread.sleep(30);
} catch (Exception ex){

}
}
g = null;
unload();
fParent.destroyApp(false);
fParent = null;
}
}




Things to Consider
You could also consider adding method wrappers in the clsFont class to simplify drawing integer types like so:


public void drawInt(Graphics g, int num, int x, int y){
drawString(g, Integer.toString(num), x, y);
}

public void drawLong(Graphics g, long num, int x, int y){
drawString(g, Long.toString(num), x, y);
}



A variant of the drawString() that draws the string aligned to the right could easily be made like the following lines:


//draws string from right to left starting at x,y
public void drawStringRev(Graphics g, String sTxt, int x, int y){
// get the strings length
int len = sTxt.length();

// set the starting position
int cx = x;

// if nothing to draw return
if (len == 0) {
return;
}

// our fail-safe
if (useDefault){
g.drawString(sTxt, x, y, Graphics.TOP | Graphics.RIGHT);
return;
}

// loop through all the characters in the string
for (int i = (len - 1); i >= 0; i--){

// get current character
char c = sTxt.charAt(i);

// get ordinal value or ASCII equivalent
int cIndex = (int)c;

// lookup the width of the character
int w = charW[cIndex];

// go to the next drawing position
cx -= (w + charS);

// draw the character
drawChar(g, cIndex, cx, y, w, charH);
}
}



Then you can add more method wrappers like this:


public void drawIntRev(Graphics g, int num, int x, int y){
drawStringRev(g, Integer.toString(num), x, y);
}

public void drawLongRev(Graphics g, long num, int x, int y){
drawStringRev(g, Long.toString(num), x, y);
}



Those additional methods could be used in drawing scores and numbers that are aligned to the right. Very useful if you want the score to grow leftwards like in score boards.


Bitmap Fonts With Word Wrap
Here is an additional method that you can play with and use to emulate word wrapping when using bitmap fonts:


// space between lines in pixels
public int lineS = 2;

// draws words that wrap between x and x1
public void drawStringWrap(Graphics g, String s, int x, int y, int x1){
int len = s.length();

// current x
int tx = x;

// current y
int ty = y;

/*
word buffer contents width -
I just thought it would be faster than
calling the String.length() method
*/
int ww = 0;

// word buffer
String sWord = "";

for (int i = 0; i < len; i++){
char c = s.charAt(i);
int cIndex = (int)c;
int cw = charW[cIndex];

if ((cIndex > 32) && (cIndex < 127)){
//if not a space and the character is printable

//add the character to the buffer
sWord += String.valueOf(c);

//compute the length of the current word
ww += cw;
} else {
//if space or non-printable character

// check if there is a word in the buffer
if (ww > 0) {

//check if the word goes past the right margin
if ((tx + ww) > x1){
// carrage return
tx = x;

// line feed
ty += (charH + lineS);
}

// draw the contents of the word buffer
drawString(g, sWord, tx, ty);
}

//move to the next position
tx += (ww + cw);
// clear the word buffer
sWord = "";
// word buffer width to zero
ww = 0;
}
}

// if there is a word remaining in the buffer then draw it
if (ww > 0) {
if ((tx + ww) > x1){
tx = x;
ty += (charH + lineS);
}
drawString(g, sWord, tx, ty);
}
}



The drawStringWrap() method parameters:
  • Graphics g - the Graphics object used to draw the bitmapfont
  • String s - the string to be drawn
  • int x - the left margin and horizontal starting position
  • int y - the vertical position where the first line is drawn
  • int x1 - the right margin where the words will be wrapped

The drawStringWrap() method makes use of the drawString() method to draw each word. The method considers the space and non-printable characters as a word delimiter. You can change the line spacing by changing the value of lineS.

To test it, add this line before the call to flushGraphics() in main loop in clsCanvas:


myFont.drawString(g, "ABCDEFG abcdefg", 10, 90);

myFont.drawStringWrap(g, "blah blah blah blah blah blah " +
"blah blah blah blah blah blah blah blah blah " +
"blah blah blah blah blah blah blah blah blah " +
"blah blah blah blah blah blah blah blah blah " +
"blah blah blah blah blah blah blah blah blah " +
"blah ", 10, 100, 166);

flushGraphics();



You should see this when you run it:

Bitmap Font With Wrapping on WTK 2.5.1


I hope the comments in the code will make the methods inner workings clear enough for you. If not, just post your questions in the comments. Here is the completed clsFont class with the extended methods:


package MyGame;

import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;

public class clsFont {
// additional space between characters
public int charS = 0;

// max clipping area
public int screenW = 176;
public int screenH = 208;

// flag: set to true to use the Graphics.drawString() method
// this is just used as a fail-safe
public boolean useDefault = false;

// height of characters
public int charH = 10;

// lookup table for character widths
public int[] charW = {
// first 32 characters
9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9,
// space
9,
// everything else XD
3, 5, 8, 8, 7, 8, 3, 5, 5, 6,
7, 3, 7, 3, 9, 6, 4, 6, 6, 6,
6, 6, 6, 6, 6, 3, 3, 6, 6, 6,
6, 9, 6, 6, 6, 6, 6, 6, 6, 6,
3, 6, 6, 6, 9, 6, 6, 6, 6, 6,
6, 7, 6, 6, 9, 6, 6, 6, 5, 9,
5, 4, 6, 4, 6, 6, 6, 6, 6, 6,
6, 6, 3, 4, 6, 3, 9, 6, 6, 6,
6, 6, 6, 6, 6, 6, 9, 6, 6, 6,
5, 3, 5, 4,
// delete
9};

// the bitmap font image
private Image imgFont;

public clsFont() {
}

public boolean load(String imagePath){
useDefault = false;
try{
// load the bitmap font
if (imgFont != null){
imgFont = null;
}
imgFont = Image.createImage(imagePath);
} catch (Exception ex){
// oohh we got an error then use the fail-safe
useDefault = true;
}
return (!useDefault);
}

public void unload(){
// make sure the object gets destroyed
imgFont = null;
}

public void drawChar(Graphics g, int cIndex, int x, int y, int w, int h){
// non printable characters don't need to be drawn
if (cIndex < 33){
return;
}

// neither does the delete character
if (cIndex > 126){
return;
}

// get the characters position
int cx = cIndex * 9;

// reset the clipping rectangle
g.setClip(0, 0, screenW, screenH);

// resize and reposition the clipping rectangle
// to where the character must be drawn
g.clipRect(x, y, w, h);

// draw the character inside the clipping rectangle
g.drawImage(imgFont, x - cx, y, Graphics.TOP | Graphics.LEFT);
}

public void drawString(Graphics g, String sTxt, int x, int y){
// get the strings length
int len = sTxt.length();

// set the starting position
int cx = x;

// if nothing to draw return
if (len == 0) {
return;
}

// our fail-safe
if (useDefault){
g.drawString(sTxt, x, y, Graphics.TOP | Graphics.LEFT);
return;
}

// loop through all the characters in the string
for (int i = 0; i < len; i++){

// get current character
char c = sTxt.charAt(i);

// get ordinal value or ASCII equivalent
int cIndex = (int)c;

// lookup the width of the character
int w = charW[cIndex];

// draw the character
drawChar(g, cIndex, cx, y, w, charH);

// go to the next drawing position
cx += (w + charS);
}
}

// extended methods ***************************************

public void drawInt(Graphics g, int num, int x, int y){
drawString(g, Integer.toString(num), x, y);
}

public void drawLong(Graphics g, long num, int x, int y){
drawString(g, Long.toString(num), x, y);
}

// Right align methods ****************************************

//draws string from right to left starting at x,y
public void drawStringRev(Graphics g, String sTxt, int x, int y){
// get the strings length
int len = sTxt.length();

// set the starting position
int cx = x;

// if nothing to draw return
if (len == 0) {
return;
}

// our fail-safe
if (useDefault){
g.drawString(sTxt, x, y, Graphics.TOP | Graphics.RIGHT);
return;
}

// loop through all the characters in the string
for (int i = (len - 1); i >= 0; i--){

// get current character
char c = sTxt.charAt(i);

// get ordinal value or ASCII equivalent
int cIndex = (int)c;

// lookup the width of the character
int w = charW[cIndex];

// go to the next drawing position
cx -= (w + charS);

// draw the character
drawChar(g, cIndex, cx, y, w, charH);

}
}

public void drawIntRev(Graphics g, int num, int x, int y){
drawString(g, Integer.toString(num), x, y);
}

public void drawLongRev(Graphics g, long num, int x, int y){
drawString(g, Long.toString(num), x, y);
}

// Word wrap method ****************************************

// space between lines in pixels
public int lineS = 2;

// draws words that wrap between x and x1
public void drawStringWrap(Graphics g, String s, int x, int y, int x1){
int len = s.length();

// current x
int tx = x;

// current y
int ty = y;

/*
word buffer contents width -
I just thought it would be faster than
calling the String.length() method
*/
int ww = 0;

// word buffer
String sWord = "";

for (int i = 0; i < len; i++){
char c = s.charAt(i);
int cIndex = (int)c;
int cw = charW[cIndex];

if ((cIndex > 32) && (cIndex < 127)){
//if not a space and the character is printable

//add the character to the buffer
sWord += String.valueOf(c);

//compute the length of the current word
ww += cw;
} else {
//if space or non-printable character

// check if there is a word in the buffer
if (ww > 0) {

//check if it goes past the right margin
if ((tx + ww) > x1){
// carrage return
tx = x;

// line feed
ty += (charH + lineS);
}

// draw the contents of the word buffer
drawString(g, sWord, tx, ty);
}

//move to the next position
tx += (ww + cw);

// clear the word buffer
sWord = "";

// word buffer width to zero
ww = 0;
}
}

// if there is a word remaining in the buffer then draw it
if (ww > 0) {
if ((tx + ww) > x1){
tx = x;
ty += (charH + lineS);
}
drawString(g, sWord, tx, ty);
}
}

}



There is a lot that could be done to make the clsFont class more functional. For instance, you can add justification or optimize the already existing methods. You can also add another set of characters to the bitmap with a different style and modify the code so you can select which style to use. Since all the building blocks are in place, I think I'll just leave the missing parts to you.

You could do better? Found an error? Is it as confusing to you as it is to me? Don't hesitate to leave a comment.

19 comments   |   post a comment
said...
Excellent post! Thanks a lot, it is a time saver for me to figure this out myself.
I've been struggling with this for quite a while, or actually on whether or not to go this route. My next step now is to start working on entry-fields etc along the same line. Displaying text is one, entering is even more important.
said...
I'm glad you liked it. Hhhmmm, you just gave me a great idea for a tutorial. Thanx!
Wonderful! From now on I will be you loyal student, I am glad u here to grow me. Keep this up I will tell u of my project when it’s done. I would love to be like u one day help rising programmers do their best. You are an inspiration. Thanks for that beautiful font to.
said...
eh he he thanx! >.<
why? are you putting the >.< thing in here now. I don't understand.
said...
It's an Asian style smiley :)
said...
How can you render characters of multiple colors with a single color font strip... Would that be possible
said...
Great post!
Thank you for saving my time... :)

You have a problem in drawChar function or in the PNG file:
the difference between the chars is 8 pixels and not 9.
Therefore, you have to change the "int cx = cIndex * 9;" line to "int cx = cIndex * 8;".

Excellent question from chunmun about the colors...
Another question: do you have some application that can create this kind of PNG files with fonts automatically (from some regular windows fonts)?
said...
Hi chunmun,

I have no idea. But after looking up the methods of the Image object from the javadocs, it may be possible to create a new modified instance of the image by using a combination of getRGB() and createRGBImage(). This means you would have to create a new image from the modified ARGB pixel data of the original image.

What I actually do to display the text with different colors is I add a new row to the existing bitmap font image. I just copy over the first row and modify the colors and styles.

The drawing functions I use are similar to what I have posted here but have one extra parameter which let's me choose which row of the bitmap font will be used to draw the characters/strings.

Good Luck!
said...
Hi micheal,

Always nice to know it was of some help =)

I have double checked the image and the code, and there seems to be nothing wrong with it...I also re-used the code for the tutorial Handling Text Input or Accepting Character/Player Names. Seems to work fine.

The characters in the bitmap font used is placed in a 9x10 pixel grid. So I'm pretty sure that each cell is 9 pixels wide.

The position of the characters are computed by multiplying the character index by the width of a cell much like you would do when working with tiles in a tile based map.

However, the widest character in the bitmap font image I used in this tutorial is actually 8 pixels so there is an extra 1 pixel of space to the right of each cell. You can still make the bitmap font image smaller by making the cells 8x10 pixels without cropping any of characters in the bitmap font.

Another question: do you have some application that can create this kind of PNG files with fonts automatically (from some regular windows fonts)?

No, I drew the bitmap fonts by hand...well not really by hand since I used pixel fonts. I usually just use Fireworks (and an ASCII table chart). I've been planning to make one but I'm pretty sure that there are already some good ones floating on the internet.

Thanks for the heads-up.
Hi Devlin,

first of all, really great tutorial, I only wish I had found this sooner, could have saved me a lot of time. I had the same problem as Micheal, and changing to "int cx = cIndex * 8;" fixed it.

In regards to an application to generate bitmap fonts. Try googling "font4mobile" I'm using that at the moment to generate fonts

Once again, great tutorial, will be visiting this site more often :)
Hello dear devlin
so thanks for this Article!
how it's possible to display a string or text as Right to Left on Mobile device using j2me?
like Arabic text direction that's right to left unlike English text direction that's left ro right
it's important for me
i will back as soon for your solutions about this problem...
said...
Hey Delvin,

Thanks for the article. It was of great help.

I have a problem with the word-wrap method however. The method doesnt consider the character spacing.

Changed this to make it work for me
//compute the length of the current word
ww += cw+charS;

// AND

//move to the next position
tx += (ww + cw+charS);
Great man!
But what if my font language in Unicode format( Khmer-Unicode ) that have​ head and foot like this ព្រះរាជាណាចក្រកម្ពុជា និង ថៃ what should i do.
Excellent post. Thanks a lot.
said...
Hi Devlin,
Great Post.
Can we display text in stringItem or textField object instead of canvas?
said...
Hi Devlin,
The tutorial is very easy to understand, it makes me very clear to understand.
One problem, I m not finding fonts.png file. Plz help me. Better to mail (misbahulalam@gmail.com) me.
said...
bravo! great!
thanks so much, it is very helpful with me.
Can you tell me where you get font image(filetype :png) ?
I searched by google but i only found TTF font..
really thanks,
my email:serpent.ty@gmail.com
This comment has been removed by the author.