Step 2: Fonts

In this step you'll add font support to the Marquee applet. First, add the fields in blue:

public class Marquee extends Applet implements Runnable {
	//
	// Fields
	//

	Thread thread = new Thread(this);

	private Dimension size;
	private Color backColor, textColor, linkColor, activeColor;

	private Font textFont, linkFont;
	private FontMetrics textFontMetrics, linkFontMetrics;

	private String linkBase;

We declare two Font objects and two FontMetrics objects. A Font object represents a font, and FontMetrics holds information about a font such as size information. Here, we have Font and FontMetrics objects for normal text and link text.

These fields are initialized when the applet starts:

public void init() {
	// Save the applet size
	size = size();

	// Get applet parameter settings
	backColor = initColor("bgcolor", Color.black);
	setBackground(backColor);

	textColor = initColor("text", Color.lightGray);
	linkColor = initColor("link", Color.yellow);
	activeColor = initColor("alink", Color.red);

	textFont = initFont("textfont", getFont());
	textFontMetrics = getFontMetrics(textFont);

	linkFont = initFont("linkfont", textFont);
	linkFontMetrics = getFontMetrics(linkFont);

	initMessageSettings();

	thread.start();
}

The new initFont() method (discussed below) has two arguments: the parameter name and the default font to use. This method will read the parameter information and return the requested font. We use the applet's default font (returned by calling getFont()) as the default for textFont, and textFont as the default for linkFont. We also call getFontMetrics() to get the font information for both fonts.

Add the new initFont() method right after initColor():

// Initialize the font for drawing
private Font initFont(String param, Font defFont) {
	String s = getParameter(param);

	// If no font is specified, use default
	if(s == null)
		return defFont;

	// Get delimiter positions
	int delim1 = s.indexOf(','), 
		delim2 = s.indexOf(',', delim1 + 1);

	if(delim1 == -1 || delim2 == -1) {
		errorMsg = "Invalid " + param + ": face,style,size (style=B,I,BI)";
		return defFont;
	}

	// Parse values
	String fontFace = defFont.getName(), fontStyles;
	int fontStyle = Font.PLAIN, fontSize = defFont.getSize();

	if(delim1 > 0)
		// Get the font typeface
		fontFace = s.substring(0, delim1);

	if(delim2 > delim1 + 1) {
		// Get the font style
		fontStyles = s.substring(delim1 + 1, delim2);

		if(fontStyles.indexOf('B') != -1)
			fontStyle |= Font.BOLD;
		if(fontStyles.indexOf('I') != -1)
			fontStyle |= Font.ITALIC;
	}

	if(delim2 < s.length() - 1) {
		try {
			// Get the font size
			fontSize = Integer.parseInt(s.substring(delim2 + 1));
		}
		catch(NumberFormatException e) {
			errorMsg = "Invalid " + param + " size";
			return defFont;
		}
	}

	// Create the font
	return new Font(fontFace, fontStyle, fontSize);
}

Marquee's Font Parameter Format and Font Terms

Before I go into the details of this complex method, let me explain the format of font parameters that Marquee expects. Basically, the format is this:

typeface,BI,size

The two commas are required, but all the other parts are optional. The typeface part (everything before the first comma) names the font to use. The Web page author can also specify B for bold and/or I for italic in the middle part—both letters are optional, and can be specified in either order. If neither is given, the font will be plain. Finally, the size part (everything after the second comma) is an integer that specifies the font size.

For example, the value "TimesRoman,B," means to use Times Roman, bold, in the default size. The typeface can be any of the following (depending on the user's system): Dialog, Helvetica, TimesRoman, Courier, or Symbol.

I'll also quickly explain some terms for fonts. The baseline is the imaginary line that text rests on. Descent is how far below the baseline a particular character extends, and ascent is how far above. Leading is the amount of space between the descent of one line and the ascent of the next line. A font's standard height is the distance between the baselines of two adjacent lines of text, and is the sum of the ascent, descent, and leading. You can use the getAscent(), getDescent(), getHeight(), and getLeading() methods of a FontMetrics object to get information about a font.

Another handy method of FontMetrics that we'll be using is stringWidth(), which returns the width of a string.

Now let's dissect initFont(), line by line.

initFont in Detail

We first call getParameter() to get the parameter value, storing it in s, and then return the default font if no value is specified:

String s = getParameter(param);

// If no font is specified, use default
if(s == null)
	return defFont;

Then we call the indexOf() method of s (class String) to get the character positions of the two commas, delim1 and delim2:

int delim1 = s.indexOf(','), delim2 = s.indexOf(',', delim1 + 1);

This method searches a string for the position of the first instance of a character. Note that we specify a single character (not a string) by enclosing it in single quotation marks ('). Also note that there are two forms of indexOf(); we can optionally specify a position to start searching from. For delim2, we search the string after the first comma. Indices start at 0, so the first character is at position 0.

If the search fails, indexOf() returns –1. If either delim1 or delim2 is –1, we set the error message (describing the correct syntax) and return the default font:

if(delim1 == -1 || delim2 == -1) {
	errorMsg = "Invalid " + param + ": face,style,size (style=B,I,BI)";
	return defFont;
}

Remember that the logical OR operator (||) return true if either of its operands are true. Also note the use of the concatenation operator (+) operator to join two or more strings.

If two commas have been found, we parse the three values. We declare three variables, fontFace, fontStyle, and fontSize, to hold information about the default font:

String fontFace = defFont.getName(), fontStyles;
int fontStyle = Font.PLAIN, fontSize = defFont.getSize();

As shown, we call getName() and getSize() to return the name and size of the font, and use the plain style constant Font.PLAIN. (As you can probably guess, use getStyle() to return the font's style).

We first check if delim1 is greater than 0. If so, meaning that a typeface has been specified, we get the value by calling the substring() method. This method returns the characters starting at the first index up to, but not including, the character at the last index:

if(delim1 > 0)
	// Get the font typeface
	fontFace = s.substring(0, delim1);

Next, we check if a font style has been specified by seeing if delim2 is greater than (delim1 + 1). If so, we extract the style using substring() and then check to see if B or I is present. If they are, then we use the OR assignment operator (|=) to combine the corresponding font style constants (Font.BOLD and Font.ITALIC):

if(fontStyles.indexOf('B') != -1)
	fontStyle |= Font.BOLD;
if(fontStyles.indexOf('I') != -1)
	fontStyle |= Font.ITALIC;

Then, we check if a font size has been specified; if delim2 is less than the last character position (one less than the length of the string, returned by length()), we parse the integer, handling NumberFormatException appropriately:

fontSize = Integer.parseInt(s.substring(delim2 + 1));

Finally, if no errors occurred, we create a new Font object with all the styles and return it:

return new Font(fontFace, fontStyle, fontSize);

In the next step, we'll use the information we gathered here to display the error message.


1