Using Fonts

Text fonts in Java are represented by instances of the java.awt.Font class. A Font object is constructed from a name, style identifier, and a point size. We can create a Font object at any time, but it’s meaningful only when applied to a particular component on a given display device. Here are a couple of fonts:

    Font smallFont = new Font("Monospaced", Font.PLAIN, 10);
    Font bigFont = new Font("Serif", Font.BOLD, 18);

Font names come in three varieties: family names, face names (also called font names), and logical names. Family and font names are closely related. For example, Garamond Italic is a font name for a font whose family name is Garamond.

A logical name is a generic name for the font family. The following logical font names should be available on all platforms:

  • Serif (generic name for TimesRoman)

  • SansSerif (generic name for Helvetica)

  • Monospaced (generic name for Courier)

  • Dialog

  • DialogInput

The logical font name is mapped to an actual font on the local platform. Java’s fonts.properties file maps the font names to the available fonts, covering as much of the Unicode character set as possible. If you request a font that doesn’t exist, you get the default font.

One of the big wins in the 2D API is that it can use most of the fonts you have installed on your computer. The following program prints out a full list of the fonts that are available to the 2D API:

    //file: ShowFonts.java
    import java.awt.*;

    public class ShowFonts {
      public static void main(String[] args) {
        Font[] fonts;
        fonts =
         GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
        for (int i = 0; i < fonts.length; i++) {
          System.out.print(fonts[i].getFontName() + " : ");
          System.out.print(fonts[i].getFamily() + " : ");
          System.out.print(fonts[i].getName());
          System.out.println();
        }
      }
    }

Note, however, that the fonts installed on your system may not match the fonts installed on someone else’s system. For true portability, you can use one of the logical names (although your application won’t look exactly the same on all platforms) or go with the defaults. Alternatively, you can test for the existence of your preferred font and fall back on a logical font, or you can allow your users to configure the application by choosing fonts themselves.

The static method Font.getFont() looks up a font by name in the system properties list just like Color.getColor(). And as with Color.getColor(), this is interesting but useless. Normally, you’ll either choose a Font from one that is available in the environment (as in the ShowFonts example) or use identifiers to describe the font you want in the Font constructor.

The Font class defines three static style identifiers: PLAIN, BOLD, and ITALIC. You can use these values on all fonts, although some fonts may not provide bold or italic versions. The point size determines the size of the font on a display. If a given point size isn’t available, Font substitutes a default size.

You can retrieve information about an existing Font with a number of routines. The getName(), getSize(), and getStyle() methods retrieve the logical name, point size, and style, respectively. You can use the getFamily() method to find out the family name, while getFontName() returns the face name of the font.

Finally, to actually use a Font object, you can simply specify it as an argument to the setFont() method of a Component or Graphics2D object. Subsequent text drawing commands such as drawString() for that component or in that graphics context use the specified font.

Font Metrics

To get detailed size and spacing information for text rendered in a font, we can ask for a java.awt.font.LineMetrics object. Different systems have different real fonts available; the available fonts may not match the font you request. Furthermore, the measurements of different characters within a single font may be different, especially in multilingual text. Thus, a LineMetrics object presents information about a particular set of text in a particular font on a particular system, not general information about a font. For example, if you ask for the metrics of a nine-point Monospaced font, what you get isn’t some abstract truth about Monospaced fonts; you get the metrics of the font that the particular system uses for nine-point Monospaced—which may not be exactly nine points or even fixed width.

Use the getLineMetrics() method for a Font to retrieve the metrics for text as it would appear for that component. This method also needs to know some information about how you plan to render the text—if you’re planning to use anti-aliasing, for instance, which affects the text measurements. This extra information is encapsulated in the FontRenderContext class. Fortunately, you can just ask Graphics2D for its current FontRenderContext rather than having to create one yourself:

    public void paint(Graphics g) {
      Graphics2D g2 = (Graphics2D)g;
      ...
      FontRenderContext frc = g2.getFontRenderContext();
      LineMetrics metrics = font.getLineMetrics("Monkey", frc);
      ...
    }

The Font class also has a getStringBounds() method that returns the bounding box of a piece of text:

    public void paint(Graphics g) {
      Graphics2D g2 = (Graphics2D)g;
      ...
      FontRenderContext frc = g2.getFontRenderContext();
      float messageWidth =
          (float)font.getStringBounds("Monkey", frc).getWidth();
      ...
    }

The following application, FontShow, displays a word and draws reference lines showing certain characteristics of its font, as shown in Figure 20-3. Clicking in the application window toggles the point size between a small and a large value.

    //file: FontShow.java
    import java.awt.*;
    import java.awt.event.*;
    import java.awt.font.*;
    import javax.swing.*;

    public class FontShow extends JComponent
    {
      private static final int PAD = 25;   // frilly line padding
      private boolean bigFont = true;
      private String message;

      public FontShow(String message) {
        this.message = message;
        addMouseListener(new MouseAdapter() {
          public void mouseClicked(MouseEvent e) {
            bigFont = !bigFont;
            repaint();
          }
        });
      }

      public void paint(Graphics g)
      {
        Graphics2D g2 = (Graphics2D)g;

        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);

        int size = bigFont ? 96 : 64;
        Font font = new Font("Dialog", Font.PLAIN, size);
        g2.setFont(font);
        int width = getSize().width;
        int height = getSize().height;

        FontRenderContext frc = g2.getFontRenderContext();
        LineMetrics metrics = font.getLineMetrics(message, frc);
        float messageWidth =
            (float)font.getStringBounds(message, frc).getWidth();

        // center text
        float ascent = metrics.getAscent();
        float descent = metrics.getDescent();
        float x = (width - messageWidth) / 2;
        float y = (height + metrics.getHeight()) / 2 - descent;

        g2.setPaint(getBackground());
        g2.fillRect(0, 0, width, height);

        g2.setPaint(getForeground());
        g2.drawString(message, x, y);

        g2.setPaint(Color.white);  // Base lines
        drawLine(g2, x - PAD, y, x + messageWidth + PAD, y);
        drawLine(g2, x, y + PAD, x, y - ascent - PAD);
        g2.setPaint(Color.green);  // Ascent line
        drawLine(g2, x - PAD, y - ascent,
                 x + messageWidth + PAD, y - ascent);
        g2.setPaint(Color.red);    // Descent line
        drawLine(g2, x - PAD, y + descent,
                 x + messageWidth + PAD, y + descent);
      }

      private void drawLine(Graphics2D g2,
          double x0, double y0, double x1, double y1) {
        Shape line = new java.awt.geom.Line2D.Double(x0, y0, x1, y1);
        g2.draw(line);
      }

      public static void main(String args[]) {
        String message = "Lemming";
        if (args.length > 0) message = args[0];

        JFrame frame = new JFrame("FontShow");
        frame.setSize(420, 300);
        frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        frame.getContentPane().add(new FontShow(message));
        frame.setVisible(true);
      }
    }
The FontShow application

Figure 20-3. The FontShow application

You can specify the text to be displayed as a command-line argument:

    % java FontShow "When in the course of human events ..."

FontShow may look a bit complicated, but there’s really not much to it. The bulk of the code is in paint(), which sets the font, draws the text, and adds a few lines to illustrate some of the font’s characteristics (metrics). For fun, we also catch mouse clicks (using an event handler defined in the constructor) and alternate the font size by setting the bigFont toggle variable and repainting.

By default, text is rendered above and to the right of the coordinates specified in the drawString() method. Think of that starting point as the origin of a coordinate system; the axes are the baselines of the font. FontShow draws these lines in white. The greatest height the characters stretch above the baseline is called the ascent and is shown by a green line. Some fonts also have parts of letters that fall below the baseline. The farthest distance any character reaches below the baseline is called the descent, which is illustrated with a red line.

We ask for the ascent and descent of our font with the LineMetrics class’s getAscent() and getDescent() methods. We also ask for the width of our string (when rendered in this font) with Font’s getStringBounds() method. This information is used to center the word in the display area. To center the word vertically, we use the height and adjust with the descent to calculate the baseline location. Table 20-2 provides a short list of methods that return useful font metrics.

Table 20-2. LineMetrics methods

Method

Description

getAscent()

Height above baseline

getDescent()

Depth below baseline

getLeading()

Standard vertical spacing between lines

getHeight()

Total line height (ascent + descent + leading)

Leading space is the padding between lines of text. The getHeight() method reports the total height of a line of text, including the leading space.

Get Learning Java, 4th Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.