Monday, December 15, 2008

Custom Monospaced Font

If you want to use custom fonts the easiest one to start with is Monospaced.
As each character has the same width a simple math can define the correct point to draw.
Lets start with just numbers, from zero (Unicode value 48) to nine (Unicode value of 57).
Below is a PNG image of a very small font with three pixels width and five pixels height.




The following class can be used to draw numbers using this image.

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

public class MonospacedFont {

private Image image;
private char firstChar;
private int numChars;
private int charWidth;
public MonospacedFont(Image image, char firstChar, int numChars) {
if (image == null) {
throw new IllegalArgumentException("image == null");
}
// the first visible Unicode character is '!' (value 33)
if (firstChar <= 33) {
throw new IllegalArgumentException("firstChar <= 33");
}
// there must be at lease one character on the image
if (numChars <= 0) {
throw new IllegalArgumentException("numChars <= 0");
}
this.image = image;
this.firstChar = firstChar;
this.numChars = numChars;
this.charWidth = image.getWidth() / this.numChars;
}
public void drawString (Graphics g, String text, int x, int y) {
// store current Graphics clip area to restore later
int clipX = g.getClipX();
int clipY = g.getClipY();
int clipWidth = g.getClipWidth();
int clipHeight = g.getClipHeight();
char [] chars = text.toCharArray();
for (int i = 0; i < chars.length; i++) {
int charIndex = chars[i] - this.firstChar;
// current char exists on the image
if (charIndex >= 0 && charIndex <= this.numChars) {
g.setClip(x, y, this.charWidth, this.image.getHeight());
g.drawImage(image, x - (charIndex * this.charWidth), y,
Graphics.TOP | Graphics.LEFT);
x += this.charWidth;
}
}

// restore initial clip area
g.setClip(clipX, clipY, clipWidth, clipHeight);
}
}

Here is a sample code that uses this class.

Image img;
try {
img = Image.createImage("/monospaced_3_5.PNG");
MonospacedFont mf = new MonospacedFont(img, '0', 10);
mf.drawString(g, "9876543210", 40, 40);
} catch (IOException e) {
e.printStackTrace();
}

From here you can go on defining bigger images with more letters. 

Wednesday, December 10, 2008

Chess: A study case

Since June/2007 I have been studying how to develop small and adaptive Java ME applications. My study case is a simple chess board and is available for free at GetJar.
Current version has help text, good usability, unselect feature and support for touch screen devices. But still does not support check mate.
I have just uploaded a new version. A single 15k jar file that supports resolutions from 128x96 up to 240x320 and higher.
Feel free to give it a try, leave a comment or better: write a review.

Wednesday, December 3, 2008

Internationalization (or just I18N - count the letters between I and N)

When developing adaptive Java ME applications one thing to take into account is the language used.
There is a System.property where you can read the current language selected on the handset.

String locale = System.getProperty("microedition.locale");

The first two letters identify the language while the last two letters identify the country. For example, "en-US" represents English on United States of America, while "pt-BR" represents Portuguese on Brazil.
A good thing is to have all your GUI Strings loaded based on the current language.
Lets say you store all those Strings in a single array and initialize it like this:

String messages [] = null;

if (locale.startsWith("pt")) {
    messages = new String [] {
        "Novo Jogo",
        "Configurações",
        "Ajuda"
    };
} else { // default language is English
    messages = new String [] {
        "New Game",
        "Settings",
        "Help"
    };
}

Then you define some constants to identify each index.

static final int MSG_NEW_GAME = 0;
static final int MSG_SETTINGS = 1;
static final int MSG_HELP = 2;

And use them like this (where menuList is an instance of List):

menuList.append(messages[MSG_NEW_GAME], null);
menuList.append(messages[MSG_SETTINGS], null);
menuList.append(messages[MSG_HELPS], null);

With this you have support for two languages in your application. The more cases you have for messages initiation based on locale, the better for your end user.
But be careful with automated translations. These softwares usually take the words too literally... Prefer to have someone proficient with the language do the translations for you.